docs: add binary audit and reverse engineering reports with supporting screenshots and fuzzing scripts

هذا الالتزام موجود في:
2026-08-23 17:42:15 +03:00
الأصل 13f849cb23
التزام e823358715
10 ملفات معدلة مع 1285 إضافات و2 حذوفات

عرض الملف

@@ -316,3 +316,767 @@ user_id=d62f9886-fced-4cf2-98e4-5b62000d4f03
*The output definitively proves input validation bypasses, symlink traversal, and proxy interception capabilities.* *The output definitively proves input validation bypasses, symlink traversal, and proxy interception capabilities.*
===================================================================== =====================================================================
``` ```
---
## 🔧 Drop-In Go Remediation Patches
> **Usage:** Each patch below is a self-contained Go file that can be added directly to the corresponding package. Import and call the exported functions from the existing command handlers to enforce the fix.
---
### PATCH-01: Client-Side Input Validation (Fixes NEW-VULN-01 & NEW-VULN-02)
**Target:** `pkg/validation/input.go` *(new file)*
```go
// Package validation provides client-side input sanitization
// for the Ghaymah CLI before values are transmitted to the backend API.
package validation
import (
"fmt"
"regexp"
)
// ValidProjectName enforces DNS-compatible naming:
// - Only lowercase alphanumeric characters and hyphens
// - Must start and end with an alphanumeric character
// - Length between 3 and 63 characters (RFC 1035)
var ValidProjectName = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$`)
// ValidateName checks that a project/app name is safe for backend consumption.
// It rejects path traversals, XSS payloads, template injections, and oversized strings.
func ValidateName(name string) error {
if len(name) < 3 {
return fmt.Errorf("name %q is too short: minimum 3 characters required", name)
}
if len(name) > 63 {
return fmt.Errorf("name %q is too long (%d chars): maximum 63 characters allowed", name, len(name))
}
if !ValidProjectName.MatchString(name) {
return fmt.Errorf(
"name %q contains invalid characters: only lowercase letters (a-z), "+
"digits (0-9), and hyphens (-) are allowed; must start and end with "+
"an alphanumeric character",
name,
)
}
return nil
}
// ValidatePort checks that a port number falls within the valid TCP range (165535).
func ValidatePort(port int) error {
if port < 1 || port > 65535 {
return fmt.Errorf(
"port %d is out of range: must be between 1 and 65535 (valid TCP port)",
port,
)
}
return nil
}
// ValidateDomain checks that a domain string is safe and well-formed.
var ValidDomain = regexp.MustCompile(`^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$`)
func ValidateDomainName(domain string) error {
if len(domain) > 253 {
return fmt.Errorf("domain %q exceeds maximum length of 253 characters", domain)
}
if !ValidDomain.MatchString(domain) {
return fmt.Errorf("domain %q is not a valid hostname", domain)
}
return nil
}
```
**Integration point in `cmd/deploy.go`:**
```go
// Add at the top of the deploy command's RunE function:
if err := validation.ValidatePort(port); err != nil {
return err
}
if name != "" {
if err := validation.ValidateName(name); err != nil {
return err
}
}
if domain != "" {
if err := validation.ValidateDomainName(domain); err != nil {
return err
}
}
```
---
### PATCH-02: Secure Token Storage via OS Keyring (Fixes NEW-VULN-03)
**Target:** `pkg/auth/securestore.go` *(new file)*
**Dependency:** `go get github.com/zalando/go-keyring`
```go
// Package auth provides secure credential storage using the native OS keychain
// instead of plaintext JSON files on disk.
package auth
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/zalando/go-keyring"
)
const (
// ServiceName identifies the credential entry in the OS keyring.
ServiceName = "ghaymah-cli"
// TokenKey is the keyring key under which the serialized token is stored.
TokenKey = "auth-token"
)
// StoredToken mirrors the existing nhost.StoredToken structure.
type StoredToken struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
RefreshTokenID string `json:"refreshTokenId,omitempty"`
User struct {
ID string `json:"id"`
Email string `json:"email"`
} `json:"user"`
}
// SaveTokenSecurely stores the auth token in the OS-native credential vault.
// Falls back to encrypted file storage if the keyring is unavailable (e.g., headless CI).
func SaveTokenSecurely(token *StoredToken) error {
data, err := json.Marshal(token)
if err != nil {
return fmt.Errorf("failed to serialize token: %w", err)
}
if err := keyring.Set(ServiceName, TokenKey, string(data)); err != nil {
// Fallback: write to file with strict 0600 permissions
return saveTokenToEncryptedFile(data)
}
// If keyring succeeded, remove any legacy plaintext file
removeLegacyTokenFile()
return nil
}
// GetTokenSecurely retrieves the auth token from the OS keyring.
// Falls back to the encrypted file if keyring is unavailable.
func GetTokenSecurely() (*StoredToken, error) {
secret, err := keyring.Get(ServiceName, TokenKey)
if err != nil {
// Fallback: try encrypted file
return getTokenFromEncryptedFile()
}
var token StoredToken
if err := json.Unmarshal([]byte(secret), &token); err != nil {
return nil, fmt.Errorf("failed to deserialize token from keyring: %w", err)
}
return &token, nil
}
// DeleteTokenSecurely removes the stored token from the keyring.
func DeleteTokenSecurely() error {
_ = keyring.Delete(ServiceName, TokenKey)
removeLegacyTokenFile()
return nil
}
// legacyTokenPath returns the path to the old plaintext token file.
func legacyTokenPath() string {
configDir := os.Getenv("XDG_CONFIG_HOME")
if configDir == "" {
home, _ := os.UserHomeDir()
configDir = filepath.Join(home, ".config")
}
return filepath.Join(configDir, "ghaymah", "cli", "nhost", "config.json")
}
// removeLegacyTokenFile securely deletes the old plaintext config.json.
func removeLegacyTokenFile() {
path := legacyTokenPath()
if _, err := os.Stat(path); err == nil {
// Overwrite with zeros before deleting to prevent recovery
if f, err := os.OpenFile(path, os.O_WRONLY, 0600); err == nil {
info, _ := f.Stat()
zeros := make([]byte, info.Size())
f.Write(zeros)
f.Close()
}
os.Remove(path)
}
}
// saveTokenToEncryptedFile is the fallback for headless/CI environments.
func saveTokenToEncryptedFile(data []byte) error {
path := legacyTokenPath()
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
// NOTE: In production, encrypt `data` with a machine-specific key
// (e.g., derived from machine-id + HKDF) before writing.
return os.WriteFile(path, data, 0600)
}
func getTokenFromEncryptedFile() (*StoredToken, error) {
data, err := os.ReadFile(legacyTokenPath())
if err != nil {
return nil, err
}
var token StoredToken
if err := json.Unmarshal(data, &token); err != nil {
return nil, err
}
return &token, nil
}
func init() {
// Print a warning if we detect a legacy plaintext token file
if _, err := os.Stat(legacyTokenPath()); err == nil {
fmt.Fprintf(os.Stderr,
"⚠️ Security: Legacy plaintext token file detected at %s\n"+
" Run 'gy login' to migrate to secure OS keyring storage.\n",
legacyTokenPath(),
)
}
_ = runtime.GOOS // suppress unused import
}
```
---
### PATCH-03: Debug Log Redaction Filter (Fixes NEW-VULN-04)
**Target:** `pkg/logger/redactor.go` *(new file)*
```go
// Package logger provides a redaction filter that masks sensitive data
// (UUIDs, Bearer tokens, emails, API keys) in debug output.
package logger
import (
"io"
"regexp"
)
// Patterns to redact from log output
var redactionPatterns = []*regexp.Regexp{
// UUIDs (e.g., user_id=6c8a1ce5-4ac3-47ad-8838-64e2a986acf3)
regexp.MustCompile(`([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})`),
// Bearer tokens
regexp.MustCompile(`(Bearer\s+)[A-Za-z0-9\-._~+/]+=*`),
// JWT tokens (eyJ...)
regexp.MustCompile(`(eyJ[A-Za-z0-9\-_]+\.eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+)`),
// Email addresses
regexp.MustCompile(`([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})`),
// API keys (long alphanumeric strings after common key patterns)
regexp.MustCompile(`((?:api[_-]?key|token|secret|password)\s*[=:]\s*)[^\s"',]+`),
}
var redactionReplacements = []string{
"${1}****-****-****-****", // UUID → partially masked
"${1}[REDACTED]", // Bearer
"[JWT-REDACTED]", // JWT
"[EMAIL-REDACTED]", // Email
"${1}[REDACTED]", // API key
}
// RedactSensitiveData applies all redaction patterns to the input string.
func RedactSensitiveData(input string) string {
result := input
for i, pattern := range redactionPatterns {
result = pattern.ReplaceAllString(result, redactionReplacements[i])
}
return result
}
// RedactingWriter wraps an io.Writer and redacts sensitive data before writing.
type RedactingWriter struct {
Underlying io.Writer
}
func (rw *RedactingWriter) Write(p []byte) (n int, err error) {
redacted := RedactSensitiveData(string(p))
return rw.Underlying.Write([]byte(redacted))
}
// NewRedactingWriter creates a writer that automatically masks sensitive data.
// Usage: pass this as the output writer for the debug logger.
//
// logger.SetOutput(NewRedactingWriter(os.Stderr))
func NewRedactingWriter(w io.Writer) *RedactingWriter {
return &RedactingWriter{Underlying: w}
}
```
**Integration in the logger initialization:**
```go
// In your logger setup (e.g., cmd/root.go or wherever slog/logrus is configured):
if debugMode {
log.SetOutput(logger.NewRedactingWriter(os.Stderr))
}
```
---
### PATCH-04: Secure Auth Client with Proxy Bypass & TLS Pinning (Fixes NEW-VULN-05)
**Target:** `pkg/auth/secureclient.go` *(new file)*
```go
// Package auth provides a hardened HTTP client for authentication endpoints
// that bypasses system proxies and enforces TLS certificate pinning.
package auth
import (
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
)
// PinnedCertHashes contains SHA-256 hashes of the SubjectPublicKeyInfo (SPKI)
// for all trusted *.ghaymah.systems certificates.
// Generate with: openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform DER | sha256sum
var PinnedCertHashes = []string{
// TODO: Replace with the actual SPKI hash of *.ghaymah.systems
// "ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67:89",
}
// AuthEndpoints lists the hostnames that must use the secure, proxy-free client.
var AuthEndpoints = []string{
"auth.ghaymah.systems",
"graphql.ghaymah.systems",
}
// isAuthEndpoint checks if the given host is a sensitive authentication endpoint.
func isAuthEndpoint(host string) bool {
// Strip port if present
h, _, err := net.SplitHostPort(host)
if err != nil {
h = host
}
for _, ep := range AuthEndpoints {
if strings.EqualFold(h, ep) {
return true
}
}
return false
}
// NewSecureAuthClient creates an HTTP client that:
// 1. Ignores HTTP_PROXY/HTTPS_PROXY for authentication endpoints
// 2. Enforces TLS 1.3 minimum
// 3. Validates server certificate SPKI hash against pinned values
// 4. Warns the user if a system proxy was detected and bypassed
func NewSecureAuthClient() *http.Client {
// Detect and warn about proxy environment
if proxy := os.Getenv("HTTPS_PROXY"); proxy != "" {
fmt.Fprintf(os.Stderr,
"⚠️ Security Warning: HTTPS_PROXY=%s detected.\n"+
" Authentication requests will bypass the proxy for security.\n"+
" Use --allow-proxy-auth to override (NOT recommended).\n",
proxy,
)
}
transport := &http.Transport{
// 1. Explicitly disable proxy for auth endpoints
Proxy: nil,
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS13,
// 2. Custom certificate verification with pinning
VerifyPeerCertificate: func(rawCerts [][]byte, chains [][]*x509.Certificate) error {
if len(PinnedCertHashes) == 0 {
// Pinning not configured yet — allow standard verification
return nil
}
for _, rawCert := range rawCerts {
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
continue
}
// Hash the SubjectPublicKeyInfo
spkiHash := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
hexHash := hex.EncodeToString(spkiHash[:])
for _, pinned := range PinnedCertHashes {
cleaned := strings.ReplaceAll(pinned, ":", "")
if strings.EqualFold(hexHash, cleaned) {
return nil // Pin matched
}
}
}
return fmt.Errorf(
"TLS certificate pinning failure: server certificate does not match " +
"any pinned SPKI hash for *.ghaymah.systems — possible MITM attack",
)
},
},
// 3. Sensible timeouts
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
}
return &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
}
```
---
### PATCH-05: Secure Auto-Update with Checksum Verification (Fixes NEW-VULN-06)
**Target:** `pkg/update/verifier.go` *(new file)*
```go
// Package update provides cryptographic integrity verification for CLI auto-updates.
package update
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
// VersionManifest represents the expected response from https://cli.ghaymah.systems/version
type VersionManifest struct {
Version string `json:"version"`
BinarySHA string `json:"sha256"` // SHA-256 hex hash of the binary
InstallerSHA string `json:"installer_sha"` // SHA-256 hex hash of install.sh
MinVersion string `json:"min_version"` // Minimum supported version
}
// VerifyFileChecksum computes the SHA-256 hash of a file and compares it
// against the expected hex-encoded hash from the version manifest.
func VerifyFileChecksum(filePath, expectedSHA256Hex string) error {
f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("cannot open file for verification: %w", err)
}
defer f.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, f); err != nil {
return fmt.Errorf("failed to read file for hashing: %w", err)
}
computedHash := hex.EncodeToString(hasher.Sum(nil))
expectedClean := strings.TrimSpace(strings.ToLower(expectedSHA256Hex))
computedClean := strings.ToLower(computedHash)
if computedClean != expectedClean {
return fmt.Errorf(
"INTEGRITY CHECK FAILED — possible supply-chain attack!\n"+
" Expected SHA-256: %s\n"+
" Computed SHA-256: %s\n"+
" File: %s\n"+
" The downloaded update has been REJECTED and deleted.",
expectedClean, computedClean, filePath,
)
}
return nil
}
// SecureDownload downloads a URL to a temporary file, verifies its checksum,
// and only then moves it to the destination path.
func SecureDownload(url, destPath, expectedSHA256 string) error {
// 1. Download to a temporary file
tmpFile, err := os.CreateTemp("", "gy-update-*")
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
tmpPath := tmpFile.Name()
defer os.Remove(tmpPath) // Clean up on any error path
client := &http.Client{Timeout: 120 * time.Second}
resp, err := client.Get(url)
if err != nil {
tmpFile.Close()
return fmt.Errorf("download failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
tmpFile.Close()
return fmt.Errorf("download failed: HTTP %d from %s", resp.StatusCode, url)
}
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
tmpFile.Close()
return fmt.Errorf("download incomplete: %w", err)
}
tmpFile.Close()
// 2. Verify checksum BEFORE moving to destination
if err := VerifyFileChecksum(tmpPath, expectedSHA256); err != nil {
return err
}
// 3. Checksum passed — atomically move to destination
if err := os.Rename(tmpPath, destPath); err != nil {
// Cross-device rename fallback: copy + delete
return copyFile(tmpPath, destPath)
}
return os.Chmod(destPath, 0755)
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
```
---
### PATCH-06: GenAI API Key Secure Input (Fixes NEW-VULN-07)
**Target:** `pkg/llm/securekey.go` *(new file)*
```go
// Package llm provides secure API key handling for GenAI integration.
package llm
import (
"bufio"
"fmt"
"os"
"strings"
"golang.org/x/term"
)
// ReadAPIKeySecurely reads the GenAI API key through a priority chain:
// 1. --ai-key-stdin flag → read from stdin (no echo, safe for piping)
// 2. OS keyring (if previously saved)
// 3. Interactive terminal prompt (masked input)
//
// This avoids exposing the key via /proc/PID/cmdline or /proc/PID/environ.
func ReadAPIKeySecurely(flagValue string) (string, error) {
// Priority 1: If explicitly passed via flag (backward compat, but warn)
if flagValue != "" {
fmt.Fprintf(os.Stderr,
"⚠️ Warning: Passing API keys via command-line flags exposes them in "+
"process listings (/proc/PID/cmdline).\n"+
" Consider using --ai-key-stdin or 'gy config set ai-key' instead.\n",
)
return flagValue, nil
}
// Priority 2: Environment variable (backward compat, but warn)
if envKey := os.Getenv("GY_AI_KEY"); envKey != "" {
fmt.Fprintf(os.Stderr,
"⚠️ Warning: GY_AI_KEY environment variable detected. This is readable "+
"via /proc/PID/environ on Linux.\n"+
" Consider using --ai-key-stdin or 'gy config set ai-key' instead.\n",
)
return envKey, nil
}
// Priority 3: Read from stdin if data is being piped
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
// Data is being piped via stdin
scanner := bufio.NewScanner(os.Stdin)
if scanner.Scan() {
return strings.TrimSpace(scanner.Text()), nil
}
return "", fmt.Errorf("no API key provided via stdin")
}
// Priority 4: Interactive secure prompt (masked)
fmt.Fprint(os.Stderr, "🔑 Enter GenAI API key: ")
keyBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr) // newline after masked input
if err != nil {
return "", fmt.Errorf("failed to read API key: %w", err)
}
key := strings.TrimSpace(string(keyBytes))
if key == "" {
return "", fmt.Errorf("API key cannot be empty")
}
return key, nil
}
```
---
### PATCH-07: Symlink-Safe File Reader (Fixes NEW-VULN-08)
**Target:** `pkg/buildpack/safefile.go` *(new file)*
```go
// Package buildpack provides safe file I/O that prevents symlink traversal attacks
// during deployment directory packaging.
package buildpack
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
// ReadFileSecurely reads a file from within baseDir, rejecting any symbolic links
// and ensuring the resolved path stays strictly inside the deployment root.
//
// This replaces direct os.ReadFile / os.Open calls in the deployment packager.
func ReadFileSecurely(baseDir, relativePath string) ([]byte, error) {
// 1. Clean and resolve the base directory
absBase, err := filepath.Abs(baseDir)
if err != nil {
return nil, fmt.Errorf("failed to resolve base directory: %w", err)
}
// 2. Construct the target path
targetPath := filepath.Join(absBase, filepath.Clean(relativePath))
// 3. Use Lstat (NOT Stat) to inspect WITHOUT following symlinks
info, err := os.Lstat(targetPath)
if err != nil {
return nil, fmt.Errorf("cannot access %s: %w", relativePath, err)
}
// 4. Reject symbolic links
if info.Mode()&os.ModeSymlink != 0 {
linkTarget, _ := os.Readlink(targetPath)
return nil, fmt.Errorf(
"🛑 SECURITY: Symbolic link detected and BLOCKED\n"+
" File: %s\n"+
" Target: %s\n"+
" Symlinks in deployment directories are not allowed to prevent "+
"local file exfiltration (CWE-59).\n"+
" Replace the symlink with the actual file content.",
relativePath, linkTarget,
)
}
// 5. Resolve the real path and verify it's inside the deployment root
realPath, err := filepath.EvalSymlinks(targetPath)
if err != nil {
return nil, fmt.Errorf("failed to resolve real path for %s: %w", relativePath, err)
}
absReal, _ := filepath.Abs(realPath)
if !strings.HasPrefix(absReal, absBase+string(filepath.Separator)) && absReal != absBase {
return nil, fmt.Errorf(
"🛑 SECURITY: Path traversal detected and BLOCKED\n"+
" File: %s\n"+
" Resolved: %s\n"+
" Deploy Root: %s\n"+
" The resolved file path is outside the deployment directory.",
relativePath, absReal, absBase,
)
}
// 6. Safe to read
return os.ReadFile(realPath)
}
// WalkDirSecurely walks a deployment directory, skipping all symlinks.
// Use this instead of filepath.WalkDir in the deployment packager.
func WalkDirSecurely(baseDir string, fn func(path string, d fs.DirEntry) error) error {
absBase, err := filepath.Abs(baseDir)
if err != nil {
return err
}
return filepath.WalkDir(absBase, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Check for symlinks via Lstat
info, lErr := os.Lstat(path)
if lErr != nil {
return lErr
}
if info.Mode()&os.ModeSymlink != 0 {
linkTarget, _ := os.Readlink(path)
fmt.Fprintf(os.Stderr,
"⚠️ Skipping symlink: %s → %s (security policy)\n",
path, linkTarget,
)
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
// Verify path stays inside base
absPath, _ := filepath.Abs(path)
if !strings.HasPrefix(absPath, absBase) {
return fmt.Errorf("path escape detected: %s is outside %s", absPath, absBase)
}
return fn(path, d)
})
}
```
---
## 📅 Remediation Roadmap & Timeline
```mermaid
gantt
title Ghaymah CLI v2 — Prioritized Remediation Roadmap
dateFormat YYYY-MM-DD
axisFormat %b %d
section 🔴 Critical (Week 12)
PATCH-04 TLS Pinning + Proxy Bypass (VULN-05) :crit, active, p4, 2026-09-01, 5d
PATCH-07 Symlink Traversal Prevention (VULN-08) :crit, active, p7, 2026-09-01, 3d
Dockerfile .dockerignore Scaffolding (VULN-03) :crit, p3, after p7, 2d
OTP + Password Change Verification (VULN-04) :crit, p4b, after p4, 4d
section 🟠 High (Week 23)
PATCH-02 OS Keyring Token Storage (VULN-03) :p2, 2026-09-08, 5d
PATCH-05 Signed Auto-Update Verification (VULN-06) :p5, 2026-09-08, 5d
PATCH-03 Debug Log Redaction Filter (VULN-04) :p3b, after p2, 3d
Token Revocation Deny-List Backend (VULN-08) :p8, after p5, 4d
section 🟡 Medium (Week 34)
PATCH-01 Port + Name Validation (VULN-01/02) :p1, 2026-09-15, 3d
PATCH-06 Secure AI Key Input (VULN-07) :p6, after p1, 2d
Deploy Timeout + Cancel Command (VULN-12) :p12, after p6, 3d
Integration Testing + Regression Suite :milestone, 2026-09-22, 0d
```
### Priority Justification
| Priority | Patches | Rationale |
| :---: | :--- | :--- |
| **P0 — Immediate** | PATCH-04 (Proxy), PATCH-07 (Symlink) | Active exploitation paths: credential theft and arbitrary file exfiltration require zero user interaction beyond a poisoned environment variable or a malicious symlink. |
| **P1 — This Sprint** | PATCH-02 (Keyring), PATCH-05 (Updates), PATCH-03 (Redactor) | Post-authentication data protection: stolen tokens enable persistent access; unsigned updates enable RCE. |
| **P2 — Next Sprint** | PATCH-01 (Validation), PATCH-06 (AI Key) | Defense-in-depth: the backend may partially validate these inputs, but client-side guards reduce attack surface and improve UX. |

عرض الملف

@@ -0,0 +1,519 @@
# 🔬 Complete Reverse Engineering Report — Ghaymah CLI v2 Binary
## Full CLI Map + `deploy` Endpoint Deep-Dive
**Binary:** `gy-linux-amd64` | **Format:** ELF 64-bit x86-64 | **Size:** 8.7 MB (9,154,722 bytes)
**Go Version:** `go1.26.6` | **CLI Version:** `0.0.24` | **Module:** `gitlab.com/ghaymahdevqateam/ghaymahcli/ghaymah-cli`
---
## 📦 1. Complete Go Dependency Tree (go.mod Reverse-Engineered)
| Dependency | Version | Purpose |
|:---|:---|:---|
| `github.com/spf13/cobra` | `v1.9.1` | CLI framework (commands, flags, aliases) |
| `github.com/spf13/pflag` | `v1.0.6` | POSIX flag parsing |
| `gitlab.com/ghaymah/go-utils` | `v0.0.0-20250705142236` | Nhost auth client + GraphQL client |
| `gitlab.com/ghaymahdevqateam/ghaymahcli/ghaymah-tunnel` | `v0.0.0` | Tunnel functionality (local replacement) |
| `github.com/flexstack/new-dockerfile` | `v0.0.0` | Auto-Dockerfile generation engine |
| `github.com/gorilla/websocket` | `v1.5.3` | WebSocket for GraphQL subscriptions |
| `github.com/google/uuid` | `v1.6.0` | UUID generation for resource IDs |
| `github.com/fatih/color` | `v1.18.0` | Terminal color output |
| `github.com/Masterminds/semver/v3` | `v3.4.0` | Semantic versioning for auto-update |
| `github.com/go-playground/validator/v10` | `v10.23.0` | Struct validation |
| `github.com/tiktoken-go/tokenizer` | `v0.6.2` | LLM tokenization for GenAI |
| `github.com/kaptinlin/jsonrepair` | `v0.1.1` | JSON repair for LLM responses |
| `github.com/dlclark/regexp2` | `v1.11.5` | Advanced regex for detection |
| `github.com/go-git/go-git/v5` | `v5.13.1` | Git operations (.gitignore parsing) |
| `github.com/gabriel-vasile/mimetype` | `v1.4.8` | File MIME type detection |
| `github.com/pelletier/go-toml/v2` | `v2.2.3` | TOML config parsing |
| `github.com/cyphar/filepath-securejoin` | `v0.4.1` | Secure filepath joining |
| `golang.org/x/crypto` | `v0.31.0` | TLS/crypto primitives |
| `golang.org/x/term` | `v0.27.0` | Terminal password input |
---
## 🗺️ 2. Complete CLI Command Map
### Internal Package Architecture (Reverse-Engineered from Symbols)
```
gitlab.com/ghaymahdevqateam/ghaymahcli/ghaymah-cli/
├── cmd/ # Cobra command handlers
│ ├── root.go # Root command + global flags (--debug, --json, --file, --no-auto-update)
│ ├── deploy.go # Deploy command handler
│ ├── config.go # Config show/set/unset/sync
│ ├── login.go # Login command
│ ├── signup.go # Signup command
│ ├── logout.go # Logout command
│ ├── whoami.go # Whoami command
│ ├── list.go # List apps/projects/integrations
│ ├── info.go # Info/inspect command
│ ├── delete.go # Delete app/project/integration
│ ├── logs.go # Log streaming command
│ ├── tunnel.go # Tunnel start/list
│ ├── version.go # Version command
│ └── completion.go # Shell completions (bash, zsh, fish, powershell)
├── pkg/
│ ├── buildpack/ # 🎯 Dockerfile generation + artifact packaging
│ │ ├── detect.go # Project type detection engine
│ │ ├── dockerfile.go # Dockerfile template generation
│ │ ├── readFileMax() # File reader (SYMLINK VULNERABLE)
│ │ └── artifact.go # tar.gz artifact creation + S3 upload
│ │
│ ├── resources/ # GraphQL resource management
│ │ ├── ResourceManager # Core CRUD manager
│ │ ├── AppResource # App resource type
│ │ ├── ProjectResource # Project resource type
│ │ ├── IntegrationResource # Integration resource type
│ │ ├── ContainerConfig # Container config struct
│ │ ├── PortConfig # Port configuration
│ │ ├── PublicAccessConfig # Public access toggle
│ │ ├── SSHConfig # SSH configuration
│ │ ├── SourceConfig # Source code config
│ │ ├── ChartInfo # Helm chart info
│ │ └── startSubscription() # WebSocket GraphQL subscription
│ │
│ ├── llm/ # GenAI / LLM integration
│ │ ├── DockerfileHint # LLM-generated Dockerfile hints
│ │ └── chatMessage # LLM chat message struct
│ │
│ ├── wizard/ # Interactive deployment wizard
│ │ ├── Run() # Wizard entry point
│ │ ├── askChoice() # Interactive choice selector
│ │ ├── askString() # String input prompt
│ │ ├── askInt() # Integer input prompt
│ │ ├── askConfirm() # Y/N confirmation
│ │ ├── askEnvVars() # Environment variable input
│ │ └── manualConfig() # Manual config fallback
│ │
│ └── config/ # Local config management (.gy.json)
│ └── ConfigManager # Config file read/write
└── main.go # Entry point
```
### Full Command Tree with Aliases and Flags
```
gy (root)
├── Global Flags:
│ ├── --debug Enable debug output (slog DEBUG level)
│ ├── -f, --file string Input file (- for stdin)
│ ├── --json Output in JSON format
│ └── --no-auto-update Skip automatic CLI updates
├── deploy [PATH] aliases: d, push, up
│ ├── -n, --name string App name (defaults to directory name)
│ ├── -p, --project string Project name (auto-creates if new)
│ ├── --port int Port (auto-detected from project type)
│ ├── --tier string Resource tier: t1, t2, t3 (default: t1)
│ ├── --public Make publicly accessible (default: true)
│ ├── --domain string Custom domain
│ ├── --dockerfile string Path to custom Dockerfile
│ ├── --ai-key string GenAI API key (or GY_AI_KEY env)
│ ├── --no-ai Disable AI-assisted detection
│ └── -w, --wizard Interactive guided setup
├── config aliases: cfg, edit, settings
│ ├── config [show] Show current .gy.json config
│ ├── config set Set values (tier, public, domain, port, env KEY=VALUE)
│ ├── config unset env KEY Remove env var
│ └── config sync Push .gy.json to server (without redeploy)
├── login aliases: l
│ ├── -e, --email string
│ └── -p, --password string
├── signup aliases: register
│ ├── -e, --email string
│ └── -p, --password string
├── logout aliases: lo
├── whoami aliases: status, me
├── list [type] aliases: ls
│ └── types: apps, projects, integrations
├── info [NAME] aliases: inspect, show, get
│ ├── --id string Look up by UUID
│ └── --project Look up a project
├── delete <type> <NAME> aliases: rm, remove, del
│ ├── types: app, project, integration
│ └── --force Skip confirmation
├── logs [NAME] aliases: log, o
│ ├── -n, --tail int Lines from end (default: 100)
│ ├── --follow Stream continuously (default: true)
│ ├── --no-follow Print and exit
│ └── --previous Previous container run logs
├── tunnel aliases: t, tun
│ ├── tunnel start NAME aliases: s, up
│ │ └── -p, --port int Local port (default: 8080)
│ └── tunnel list aliases: ls
├── version Show CLI version
└── completion Generate shell completions (bash/zsh/fish/powershell)
```
---
## 🎯 3. `deploy` Endpoint — Complete Deep-Dive
### 3.1 Deploy Execution Flow (Step-by-Step)
The deploy command orchestrates a **10-step pipeline**. Here is the exact sequence reconstructed from binary symbols, debug output, and string analysis:
```
┌─────────────────────────────────────────────────────────────────────────┐
│ gy deploy [PATH] — FULL PIPELINE │
└─────────────────────────────────────────────────────────────────────────┘
Step 1: AUTO-UPDATE CHECK
│ → GET https://cli.ghaymah.systems/version
│ → Compare semver (Masterminds/semver)
│ → If newer: download https://cli.ghaymah.systems/install.sh
│ → Execute & restart (NO signature check ⚠️ VULN-06)
│ → Skip if --no-auto-update
Step 2: AUTHENTICATION
│ → Load token from ~/.config/ghaymah/cli/nhost/config.json
│ → nhost.tokenManager.getToken()
│ → If expired: POST https://auth.ghaymah.systems/token (refresh)
│ → If no token: error "run 'gy login' first"
│ → Respects HTTP_PROXY/HTTPS_PROXY (⚠️ VULN-05)
Step 3: RESOLVE DEPLOY PATH
│ → Default: current working directory
│ → Or: explicit PATH argument
│ → Read .gy.json if present (name, port, project, env vars)
│ → Derive app name from directory basename if --name not set
Step 4: PROJECT TYPE DETECTION (pkg/buildpack/detect.go)
│ → Scan files in deploy directory:
│ │ ├── go.mod, main.go → Go
│ │ ├── package.json → Node.js (then check framework)
│ │ │ ├── next.config.* → Next.js (Standalone)
│ │ │ ├── "express" in deps → Express
│ │ │ ├── "fastify" in deps → Fastify
│ │ │ └── (default) → Node.js generic
│ │ ├── requirements.txt/Pipfile/pyproject.toml/uv.lock → Python
│ │ │ ├── manage.py → Django
│ │ │ ├── fastapi → FastAPI
│ │ │ └── (default) → Python generic
│ │ ├── Gemfile → Ruby (Rails/Sinatra)
│ │ ├── mix.exs → Elixir (Phoenix)
│ │ ├── Cargo.toml → Rust
│ │ ├── pom.xml/build.gradle → Java (Maven/Gradle)
│ │ ├── composer.json → PHP (Laravel/Symfony)
│ │ ├── deno.json/deno.lock → Deno
│ │ ├── bun.lockb → Bun
│ │ ├── index.html / nginx.conf → Static Site
│ │ └── (none matched) → Unknown
│ │
│ → If --wizard: interactive wizard.Run() for manual config
│ → If --ai-key or GY_AI_KEY: call GenAI for detection assistance
│ → Output: "Detected: {framework}" or "Unknown"
Step 5: DOCKERFILE RESOLUTION
│ → Check 1: --dockerfile flag → use custom Dockerfile path
│ → Check 2: Existing Dockerfile in deploy dir → "Detected: Existing Dockerfile"
│ → Check 3: Auto-generate from detected project type
│ │ ├── Uses github.com/flexstack/new-dockerfile library
│ │ ├── Selects template based on detection (13 templates):
│ │ │ Node.js, Next.js, Python, Go, Ruby, Rust, Java,
│ │ │ PHP, Elixir, Deno, Bun, Static Site, Generic
│ │ ├── Templates use multi-stage builds:
│ │ │ FROM {base}:{version} AS builder
│ │ │ ... install deps, build ...
│ │ │ FROM {runtime}:{version}
│ │ │ COPY --from=builder ...
│ │ │ EXPOSE {port}
│ │ │ CMD [...]
│ │ ├── Applies nonroot user (addgroup/adduser --system nonroot)
│ │ └── Writes generated Dockerfile to deploy directory
│ → If detection failed: "A Dockerfile was not detected ... could not auto-generate"
Step 6: PORT DETECTION
│ → Priority 1: --port flag value
│ → Priority 2: .gy.json port field
│ → Priority 3: Auto-detect from project type:
│ │ ├── Node.js/Next.js/Express/Fastify → 3000
│ │ ├── Python/Django/Flask/FastAPI → 8000
│ │ ├── Go → 8080
│ │ ├── Ruby/Rails → 3000
│ │ ├── PHP → 80
│ │ ├── Java → 8080
│ │ ├── Static Site → 80
│ │ └── Default → 8080
│ → Warning: "Auto-detected port: %d. If your app listens on a DIFFERENT port..."
│ → NO PORT VALIDATION (⚠️ VULN-01 — accepts -1, 99999, etc.)
Step 7: GRAPHQL CONNECTION + PROJECT SETUP
│ → Connect via WebSocket: wss://graphql.ghaymah.systems/v1/graphql
│ → Using github.com/gorilla/websocket
│ → Initialize resources.ResourceManager
│ → Start subscription handler (goroutine):
│ │ ResourceManager.startSubscription() → handleSubscription()
│ │ → processSubscriptionData() → parseResource()
│ │
│ → If project doesn't exist:
│ │ GraphQL Mutation:
│ │ mutation {
│ │ insert_ghaymah_cloud_resources_one(object: $object) {
│ │ id, name, status, ...
│ │ }
│ │ }
│ │ → POST https://graphql.ghaymah.systems/v1/graphql
│ │ → Waits for resource sync: WaitForResourceSync()
│ │
│ → Reads user info from stored token (user_id UUID)
Step 8: ARTIFACT BUILD (pkg/buildpack)
│ → "Building deployment artifact..."
│ → Create tar.gz archive of deploy directory:
│ │ ├── archive/tar.NewWriter()
│ │ ├── compress/gzip.NewWriter()
│ │ ├── Walk directory files
│ │ ├── Read files via readFileMax() (⚠️ FOLLOWS SYMLINKS — VULN-08)
│ │ ├── Include: source code, Dockerfile, .gy.json, .env files
│ │ ├── Exclude: .git directory, node_modules (via .gitignore)
│ │ └── Bundle as deployment artifact (.tar.gz)
│ │
│ → Upload artifact to S3-compatible storage:
│ │ POST https://s3-nhost-proxy-83e02743fd61.hosted.ghaymah.systems/files
│ │ → Bucket: "ghaymah-s3-bucket"
│ │ → Auth: Bearer token from nhost session
│ │ → Returns: artifact file ID / URL
Step 9: CREATE/UPDATE APP RESOURCE
│ → GraphQL Mutation (insert or update):
│ │ insert_ghaymah_cloud_resources_one(object: {
│ │ name: "app-name",
│ │ userId: "d62f9886-...",
│ │ chart_name: "app",
│ │ values: {
│ │ container: {
│ │ image: "registry.ghaymah.systems/{user}/{app}:latest",
│ │ port: [{containerPort: 8080}]
│ │ },
│ │ source: { artifactId: "...", dockerfile: "..." },
│ │ publicAccess: { enabled: true/false },
│ │ env: { KEY: "VALUE", ... },
│ │ tier: "t1" | "t2" | "t3"
│ │ }
│ │ })
│ │
│ → The mutation triggers the backend build pipeline:
│ │ Backend pulls artifact from S3 → builds Docker image →
│ │ pushes to registry.ghaymah.systems/{user}/{app}:latest →
│ │ deploys to Kubernetes cluster
Step 10: WAIT FOR DEPLOYMENT + STREAM STATUS
│ → ResourceManager.WaitForResourceSync()
│ → Subscribe to resource status updates via WebSocket
│ → Poll status: "pending" → "building" → "deploying" → "running"
│ → Timeout: 15 minutes ("deployment timed out after 15 minutes")
│ → On stuck: "Your app might be stuck building. Check 'gy logs'"
│ → On success: display app URL "https://{name}.{domain}"
│ → On failure: "deployment failed: %s"
└────────────────────────────────────────────────────────────────────
```
### 3.2 Deploy Data Flow Diagram
```
┌──────────────┐ ┌─────────────────────┐ ┌──────────────────────┐
│ Local Files │ │ Ghaymah CLI v2 │ │ Ghaymah Cloud │
│ │ │ (gy-linux-amd64) │ │ │
│ ./ │ │ │ │ │
│ ├── src/ │────▶│ 1. Detect project │ │ │
│ ├── .gy.json │ │ 2. Gen Dockerfile │ │ │
│ ├── .env │ │ 3. Create tar.gz │ │ │
│ └── Dockerfile│ │ 4. Upload artifact │────▶│ S3 Storage │
│ │ │ │ │ (s3-nhost-proxy) │
│ │ │ 5. GraphQL mutation │────▶│ Hasura GraphQL API │
│ │ │ │ │ (graphql.ghaymah.systems)│
│ │ │ 6. Subscribe status │◀──▶│ │
│ │ │ (WebSocket) │ │ Build Pipeline │
│ │ │ │ │ ├── Pull artifact │
│ │ │ 7. Stream updates │◀───│ ├── docker build │
│ │ │ "building..." │ │ ├── docker push │
│ │ │ "deploying..." │ │ │ → registry.ghaymah│
│ │ │ "running ✅" │ │ └── k8s deploy │
│ │ │ │ │ │
│ │ │ 8. Print app URL │ │ App running at: │
│ │ │ https://{name}... │ │ https://{name}.hosted│
└──────────────┘ └─────────────────────┘ └──────────────────────┘
```
### 3.3 Infrastructure Endpoints Used by Deploy
| # | Endpoint | Protocol | Purpose in Deploy Flow |
|:-:|:---|:---|:---|
| 1 | `https://auth.ghaymah.systems/token` | HTTPS POST | Token refresh before API calls |
| 2 | `wss://graphql.ghaymah.systems/v1/graphql` | WebSocket | Real-time resource subscriptions |
| 3 | `https://graphql.ghaymah.systems/v1/graphql` | HTTPS POST | GraphQL mutations (create/update resources) |
| 4 | `https://s3-nhost-proxy-83e02743fd61.hosted.ghaymah.systems/files` | HTTPS POST | Artifact upload (tar.gz of deploy directory) |
| 5 | `registry.ghaymah.systems/{user}/{app}:latest` | Docker Registry | Backend pushes built image here |
| 6 | `https://genai.ghaymah.systems` | HTTPS POST | AI-assisted project detection (optional) |
| 7 | `https://cli.ghaymah.systems/version` | HTTPS GET | Pre-deploy auto-update check |
### 3.4 GraphQL Mutation Format (Reconstructed)
The core deploy mutation, reconstructed from the string `insert_ghaymah_cloud_resources_one`:
```graphql
mutation CreateApp($object: ghaymah_cloud_resources_insert_input!) {
insert_ghaymah_cloud_resources_one(object: $object) {
id
name
status
message
chart_name
created_at
updated_at
}
}
```
**Variables payload:**
```json
{
"object": {
"name": "my-app",
"userId": "d62f9886-fced-4cf2-98e4-5b62000d4f03",
"chart_name": "app",
"values": {
"container": {
"image": "registry.ghaymah.systems/d62f9886/my-app:latest",
"port": [{"containerPort": 8080}]
},
"source": {
"artifactId": "uploaded-file-id",
"dockerfile": "Dockerfile"
},
"publicAccess": {"enabled": true},
"env": {
"DATABASE_URL": "postgres://...",
"NODE_ENV": "production"
},
"tier": "t1"
}
}
}
```
### 3.5 Resource Status Lifecycle (WebSocket Subscription)
```
┌─────────┐
│ PENDING │ ← Resource just created via mutation
└────┬────┘
┌─────────┐
│BUILDING │ ← Backend pulling artifact from S3,
│ │ running docker build
└────┬────┘
┌────┴────┐
│ │
▼ ▼
┌────────┐ ┌────────┐
│DEPLOYING│ │ FAILED │ ← Build error / Dockerfile issue
│ │ └────────┘
└────┬───┘
┌────────┐
│RUNNING │ ← App is live at https://{name}.hosted.ghaymah.systems
└────────┘
Timeout: 15 minutes → "deployment timed out"
Stuck: → "Your app might be stuck building. Check 'gy logs'"
```
### 3.6 Auto-Generated Dockerfile Templates (13 Supported)
The CLI uses `github.com/flexstack/new-dockerfile` to generate production-ready multi-stage Dockerfiles:
| # | Project Type | Detection Files | Base Image | Template Features |
|:-:|:---|:---|:---|:---|
| 1 | **Node.js** | `package.json` | `node:{v}-alpine` | Multi-stage, frozen lockfiles, nonroot user |
| 2 | **Next.js** | `next.config.*` | `node:{v}-alpine` | Standalone output, `.next/static` copy, telemetry disabled |
| 3 | **Bun** | `bun.lockb` | `oven/bun:1` | Bun-native install, frozen lockfile |
| 4 | **Deno** | `deno.json*` | `denoland/deno:2` | `deno cache`, DENO_DIR setup |
| 5 | **Python** | `requirements.txt`, `pyproject.toml`, `uv.lock`, `Pipfile` | `python:{v}-slim` | pip/uv install, PYTHONUNBUFFERED |
| 6 | **Go** | `go.mod`, `main.go` | `golang:{v}-alpine` | CGO_ENABLED=0, static binary, scratch/alpine runtime |
| 7 | **Ruby** | `Gemfile` | `ruby:{v}-slim` | Bundle install, Rails server |
| 8 | **Rust** | `Cargo.toml` | `rust:1-slim` | Cargo build --release, debian-slim runtime |
| 9 | **Java (Maven)** | `pom.xml` | `maven:{v}-eclipse-temurin` | mvn package, JRE runtime |
| 10 | **Java (Gradle)** | `build.gradle` | `gradle:{v}-jdk` | gradle build, JRE runtime |
| 11 | **Elixir** | `mix.exs` | `elixir:{v}-slim` | mix release, OTP runtime |
| 12 | **PHP** | `composer.json` | `php:{v}-apache` | Composer install, Apache/FPM |
| 13 | **Static Site** | `index.html`, `nginx.conf` | `nginx:alpine` | Direct copy to /var/www/html |
All templates feature:
- `nonroot` user creation (`addgroup --system nonroot && adduser --system`)
- `COPY --chown=nonroot:nonroot` for proper ownership
- Multi-stage builds to minimize final image size
- Frozen lockfile enforcement for reproducibility
### 3.7 `.gy.json` Configuration File Format
```json
{
"app": "my-cool-api",
"port": 3000,
"project": "my-project",
"tier": "t1",
"publicAccess": {
"enabled": true
},
"domain": "api.mydomain.com",
"env": {
"DATABASE_URL": "postgres://user:pass@host:5432/db",
"NODE_ENV": "production",
"DEBUG": "false"
}
}
```
### 3.8 Resource Tiers
| Tier | Label | Resources |
|:---:|:---|:---|
| `t1` | Default (free/starter) | Minimal CPU/RAM |
| `t2` | Small | Increased limits |
| `t3` | Production | Full resources |
### 3.9 Security Vulnerabilities in the Deploy Flow
| Deploy Step | Vulnerability | Severity | Reference |
|:---|:---|:---:|:---|
| Step 1 (Auto-Update) | No signature verification on downloaded binary | 🟠 HIGH | VULN-06 |
| Step 2 (Auth) | Proxy intercepts credentials (no TLS pinning) | 🔴 CRITICAL | VULN-05 |
| Step 6 (Port) | No client-side port validation | 🟡 MEDIUM | VULN-01 |
| Step 7 (GraphQL) | No client-side name sanitization | 🟡 MEDIUM | VULN-02 |
| Step 8 (Artifact) | Follows symlinks → arbitrary file exfiltration | 🔴 CRITICAL | VULN-08 |
| Step 8 (Artifact) | .env files included in tar.gz upload | 🔴 CRITICAL | VULN-03 |
| Step 9 (App Create) | GenAI API key in env/cmdline | 🟡 MEDIUM | VULN-07 |
---
## 📊 4. Complete Strings Extraction Summary
| Category | Count | Key Findings |
|:---|:---:|:---|
| **Total extractable strings** | ~102,944 | Full string dump from ELF binary |
| **Hardcoded API endpoints** | 8 | Auth, GraphQL, S3, GenAI, CLI, Registry, Logs, Version |
| **Dockerfile templates** | 13 | Complete multi-stage Dockerfiles for all supported languages |
| **Go package symbols** | ~90 | Full function signatures for all internal packages |
| **Go dependencies (go.mod)** | 29 | All versioned deps extracted from buildinfo |
| **GraphQL operation** | 1 | `insert_ghaymah_cloud_resources_one` |
| **Error messages** | 50+ | Complete error strings for all failure paths |
| **Framework detection markers** | 30+ | File/keyword markers for all 13 project types |

عرض الملف

@@ -2,7 +2,7 @@
BIN="/root/ghaymah-v2-test/gy-linux-amd64" BIN="/root/ghaymah-v2-test/gy-linux-amd64"
echo "=== Starting CLI Flag Fuzzing Audit ===" echo "=== Starting CLI Flag Fuzzing Audit ==="
COMMANDS="deploy config login list logs tunnel info delete" COMMANDS="deploy config login list logs tunnel info delete version whoami"
FLAGS="--file --json --debug --no-auto-update" FLAGS="--file --json --debug --no-auto-update"
TESTS=0 TESTS=0
@@ -12,7 +12,7 @@ for cmd in $COMMANDS; do
for flag in $FLAGS; do for flag in $FLAGS; do
for p in ";id" "&&ls" "|cat /etc/passwd" '$(whoami)' "NULL_BYTE_\x00" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; do for p in ";id" "&&ls" "|cat /etc/passwd" '$(whoami)' "NULL_BYTE_\x00" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; do
TESTS=$((TESTS + 1)) TESTS=$((TESTS + 1))
OUT=$($BIN $cmd $flag "$p" 2>&1) OUT=$(timeout 2 $BIN $cmd $flag "$p" --no-auto-update 2>&1)
if echo "$OUT" | grep -qiE "(panic|runtime error|segmentation fault|nil pointer)"; then if echo "$OUT" | grep -qiE "(panic|runtime error|segmentation fault|nil pointer)"; then
echo "[CRITICAL PANIC DETECTED] gy $cmd $flag '$p'" echo "[CRITICAL PANIC DETECTED] gy $cmd $flag '$p'"
echo "Output snippet: $(echo "$OUT" | head -n 3)" echo "Output snippet: $(echo "$OUT" | head -n 3)"

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 218 KiB

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 52 KiB

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 35 KiB

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 134 KiB

ثنائية
screenshots/docker.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 30 KiB

ثنائية
screenshots/env.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 31 KiB

ثنائية
screenshots/logs.png Normal file

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 15 KiB