Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.26.2']
go-version: ['1.25', '1.26.2']
steps:
- uses: actions/checkout@v4 # v4.2.2
- uses: actions/setup-go@v5 # v5.3.0
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,22 @@ procscope will detect missing capabilities at startup and provide actionable gui
|---------|--------|
| GitHub releases | Available |
| `go install` | Available |
| Debian / Kali / Parrot packages | Packaging metadata maintained in-tree; not yet shipped by the distro |
| **Homebrew (macOS/Linux)** | **Available via `Mutasem-mk4/kharma` tap** |
| Arch / BlackArch package | Available in BlackArch |
| Debian / Kali / Parrot packages | Packaging metadata maintained in-tree; pending distro inclusion |

## Installation

Note: Running procscope usually requires `sudo` (eBPF capabilities).

### 1. Go Install
### 1. Homebrew (Recommended)

```bash
brew tap Mutasem-mk4/kharma
brew install procscope
```

### 2. Go Install

```bash
go install github.com/Mutasem-mk4/procscope/cmd/procscope@latest
Expand Down
32 changes: 16 additions & 16 deletions arch/.SRCINFO
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
pkgbase = procscope
pkgdesc = Process-scoped runtime investigation tool using eBPF
pkgver = 1.1.0
pkgrel = 1
url = https://github.com/Mutasem-mk4/procscope
arch = x86_64
arch = aarch64
groups = blackarch
groups = blackarch-defensive
groups = blackarch-forensic
license = MIT
makedepends = go>=1.26.2
source = procscope-1.1.0.tar.gz::https://github.com/Mutasem-mk4/procscope/archive/v1.1.0.tar.gz
sha512sums = f8483681b1f3b6349e65d668aec67ab02bb7a0dced4f86478280561f23cdffbf139d50ba275cbf1ce17062c045b2e944f674c5c108efa38d50e752cc2e5d48bd
pkgname = procscope
pkgbase = procscope
pkgdesc = Process-scoped runtime investigation tool using eBPF
pkgver = 1.1.0
pkgrel = 1
url = https://github.com/Mutasem-mk4/procscope
arch = x86_64
arch = aarch64
groups = blackarch
groups = blackarch-defensive
groups = blackarch-forensic
license = MIT
makedepends = go>=1.26.2
source = procscope-1.1.0.tar.gz::https://github.com/Mutasem-mk4/procscope/archive/v1.1.0.tar.gz
sha512sums = f8483681b1f3b6349e65d668aec67ab02bb7a0dced4f86478280561f23cdffbf139d50ba275cbf1ce17062c045b2e944f674c5c108efa38d50e752cc2e5d48bd

pkgname = procscope
102 changes: 51 additions & 51 deletions arch/PKGBUILD
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
# This file is part of BlackArch Linux ( https://www.blackarch.org/ ).
# See COPYING for license details.
pkgname=procscope
pkgver=1.1.0
pkgrel=1
pkgdesc='Process-scoped runtime investigation tool using eBPF'
arch=('x86_64' 'aarch64')
groups=('blackarch' 'blackarch-defensive' 'blackarch-forensic')
url='https://github.com/Mutasem-mk4/procscope'
license=('MIT')
makedepends=('go>=1.26.2')
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz")
sha512sums=('f8483681b1f3b6349e65d668aec67ab02bb7a0dced4f86478280561f23cdffbf139d50ba275cbf1ce17062c045b2e944f674c5c108efa38d50e752cc2e5d48bd')
build() {
cd "${pkgname}-${pkgver}"
export CGO_ENABLED=0
export GOFLAGS="-trimpath -mod=readonly -modcacherw"
go build \
-ldflags "-s -w \
-X 'github.com/Mutasem-mk4/procscope/internal/version.Version=${pkgver}' \
-X 'github.com/Mutasem-mk4/procscope/internal/version.Commit=blackarch'" \
-o "${pkgname}" \
./cmd/procscope
}
check() {
cd "${pkgname}-${pkgver}"
go test -short ./internal/events/... ./internal/output/... ./internal/redact/... ./internal/version/...
}
package() {
cd "${pkgname}-${pkgver}"
install -Dm755 "${pkgname}" "${pkgdir}/usr/bin/${pkgname}"
install -Dm644 "man/${pkgname}.1" "${pkgdir}/usr/share/man/man1/${pkgname}.1"
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
install -Dm644 "completions/${pkgname}.bash" \
"${pkgdir}/usr/share/bash-completion/completions/${pkgname}"
install -Dm644 "completions/${pkgname}.zsh" \
"${pkgdir}/usr/share/zsh/site-functions/_${pkgname}"
install -Dm644 "completions/${pkgname}.fish" \
"${pkgdir}/usr/share/fish/vendor_completions.d/${pkgname}.fish"
install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}"
}
# This file is part of BlackArch Linux ( https://www.blackarch.org/ ).
# See COPYING for license details.

pkgname=procscope
pkgver=1.1.0
pkgrel=1
pkgdesc='Process-scoped runtime investigation tool using eBPF'
arch=('x86_64' 'aarch64')
groups=('blackarch' 'blackarch-defensive' 'blackarch-forensic')
url='https://github.com/Mutasem-mk4/procscope'
license=('MIT')
makedepends=('go>=1.26.2')
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/v${pkgver}.tar.gz")
sha512sums=('f8483681b1f3b6349e65d668aec67ab02bb7a0dced4f86478280561f23cdffbf139d50ba275cbf1ce17062c045b2e944f674c5c108efa38d50e752cc2e5d48bd')

build() {
cd "${pkgname}-${pkgver}"

export CGO_ENABLED=0
export GOFLAGS="-trimpath -mod=readonly -modcacherw"

go build \
-ldflags "-s -w \
-X 'github.com/Mutasem-mk4/procscope/internal/version.Version=${pkgver}' \
-X 'github.com/Mutasem-mk4/procscope/internal/version.Commit=blackarch'" \
-o "${pkgname}" \
./cmd/procscope
}

check() {
cd "${pkgname}-${pkgver}"

go test -short ./internal/events/... ./internal/output/... ./internal/redact/... ./internal/version/...
}

package() {
cd "${pkgname}-${pkgver}"

install -Dm755 "${pkgname}" "${pkgdir}/usr/bin/${pkgname}"
install -Dm644 "man/${pkgname}.1" "${pkgdir}/usr/share/man/man1/${pkgname}.1"
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"

install -Dm644 "completions/${pkgname}.bash" \
"${pkgdir}/usr/share/bash-completion/completions/${pkgname}"
install -Dm644 "completions/${pkgname}.zsh" \
"${pkgdir}/usr/share/zsh/site-functions/_${pkgname}"
install -Dm644 "completions/${pkgname}.fish" \
"${pkgdir}/usr/share/fish/vendor_completions.d/${pkgname}.fish"

install -Dm644 README.md -t "${pkgdir}/usr/share/doc/${pkgname}"
}
41 changes: 41 additions & 0 deletions docs/marketing/autogen/launch-v1.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Launch Copy Pack (v1.1.0)

Generated: 2026-04-27 (UTC)

## X/Twitter (single post)

v1.1.0 of procscope is out.

Process-scoped eBPF tracing for Linux incident response:
- trace suspicious binaries without ptrace overhead
- generate JSONL + evidence bundles + markdown reports
- ship as a single binary

Release: https://github.com/Mutasem-mk4/procscope/releases/tag/v1.1.0
Repo: https://github.com/Mutasem-mk4/procscope

#eBPF #Linux #CyberSecurity #OpenSource

## LinkedIn

I just shipped v1.1.0 of procscope.

procscope helps incident responders and security engineers trace what a Linux process actually did, with process-scoped eBPF visibility and low-noise outputs designed for triage.

Highlights:
- process-tree scoped tracing
- event timeline + JSONL + evidence bundle outputs
- easy install from GitHub releases

Release notes: https://github.com/Mutasem-mk4/procscope/releases/tag/v1.1.0
GitHub: https://github.com/Mutasem-mk4/procscope

## Dev.to / Blog Intro Paragraph

Today I released v1.1.0 of procscope, a process-scoped eBPF tracer built for malware triage and incident response. Instead of collecting host-wide noise, procscope follows the specific process tree you care about and outputs investigation-ready artifacts (timeline, JSONL, bundle, and summary) you can share with your team.

## Manual-only Channels Checklist

- [ ] Hacker News `Show HN` post submitted
- [ ] Reddit post in relevant subreddit submitted
- [ ] Replies monitored during first 24h
35 changes: 35 additions & 0 deletions fix_line_endings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import os

def check_and_convert(file_path):
with open(file_path, 'rb') as f:
content = f.read()

if b'\r\n' in content:
print(f"Converting {file_path} to LF...")
new_content = content.replace(b'\r\n', b'\n')
with open(file_path, 'wb') as f:
f.write(new_content)
return True
return False

files_to_check = [
'arch/PKGBUILD',
'arch/.SRCINFO',
'Makefile',
]

# Add scripts and debian files
for root, dirs, files in os.walk('.'):
if '.git' in dirs:
dirs.remove('.git')
for file in files:
if root.startswith('./scripts') or root.startswith('./debian') or file.endswith('.sh'):
files_to_check.append(os.path.join(root, file))

converted_count = 0
for f in set(files_to_check):
if os.path.isfile(f):
if check_and_convert(f):
converted_count += 1

print(f"Total files converted: {converted_count}")
36 changes: 18 additions & 18 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
if !opts.SkipChecks {
result := caps.Check()
if !result.CanProceed() {
_, _ = _, _ = _, _ = fmt.Fprintln(os.Stderr, result.Summary())
fmt.Fprintln(os.Stderr, result.Summary())
return fmt.Errorf("privilege check failed — use --skip-checks to override (may cause load failures)")
}
if len(result.Warnings) > 0 {
for _, w := range result.Warnings {
_, _ = _, _ = _, _ = fmt.Fprintf(os.Stderr, "⚠ %s\n", w)
fmt.Fprintf(os.Stderr, "⚠ %s\n", w)
}
}
}
Expand Down Expand Up @@ -188,7 +188,7 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
}
targetPID = pids[0]
if len(pids) > 1 {
_, _ = _, _ = fmt.Fprintf(os.Stderr, "⚠ Multiple processes match '%s', attaching to PID %d\n",
fmt.Fprintf(os.Stderr, "⚠ Multiple processes match '%s', attaching to PID %d\n",
opts.ProcessName, targetPID)
}
}
Expand All @@ -201,7 +201,7 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
_, _ = _, _ = fmt.Fprintln(os.Stderr, "\n⏹ Stopping investigation...")
fmt.Fprintln(os.Stderr, "\n⏹ Stopping investigation...")
cancel()
}()

Expand All @@ -214,14 +214,14 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
// Spin up Kubernetes watcher if requested
var watcher *k8s.Watcher
if opts.K8s {
_, _ = _, _ = fmt.Fprintln(os.Stderr, "🔄 Initializing Kubernetes pod metadata watcher...")
fmt.Fprintln(os.Stderr, "🔄 Initializing Kubernetes pod metadata watcher...")
var err error
watcher, err = k8s.NewWatcher(ctx)
if err != nil {
return fmt.Errorf("kubernetes initialization failed: %w", err)
}
correlator.SetK8sResolver(watcher)
_, _ = _, _ = fmt.Fprintln(os.Stderr, "✅ Kubernetes integration established")
fmt.Fprintln(os.Stderr, "✅ Kubernetes integration established")
}

// Initialize eBPF tracer
Expand Down Expand Up @@ -256,9 +256,9 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
return fmt.Errorf("failed to resume command: %w", err)
}

_, _ = _, _ = fmt.Fprintf(os.Stderr, "🔍 procscope investigation %s\n", investigationID)
_, _ = _, _ = fmt.Fprintf(os.Stderr, " Command: %s\n", commandLine)
_, _ = _, _ = fmt.Fprintf(os.Stderr, " PID: %d\n\n", targetPID)
fmt.Fprintf(os.Stderr, "🔍 procscope investigation %s\n", investigationID)
fmt.Fprintf(os.Stderr, " Command: %s\n", commandLine)
fmt.Fprintf(os.Stderr, " PID: %d\n\n", targetPID)
} else {
// Attach mode: track existing PID and children
if err := mgr.TrackPID(targetPID); err != nil {
Expand All @@ -276,9 +276,9 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
}
}

_, _ = _, _ = fmt.Fprintf(os.Stderr, "🔍 procscope investigation %s\n", investigationID)
_, _ = _, _ = fmt.Fprintf(os.Stderr, " Attached to PID: %d\n", targetPID)
_, _ = _, _ = fmt.Fprintf(os.Stderr, " Press Ctrl+C to stop.\n\n")
fmt.Fprintf(os.Stderr, "🔍 procscope investigation %s\n", investigationID)
fmt.Fprintf(os.Stderr, " Attached to PID: %d\n", targetPID)
fmt.Fprintf(os.Stderr, " Press Ctrl+C to stop.\n\n")
}

// Set up output sinks
Expand All @@ -296,7 +296,7 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
timeline := output.NewTimeline(colorize)

if !opts.Quiet {
_, _ = _, _ = fmt.Fprintln(os.Stderr, timeline.Header())
fmt.Fprintln(os.Stderr, timeline.Header())
}

// Collect all events for bundle/summary
Expand All @@ -319,7 +319,7 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
for evt := range correlator.Events() {
// Timeline output
if !opts.Quiet {
_, _ = _, _ = fmt.Fprintln(os.Stderr, timeline.RenderEvent(evt))
fmt.Fprintln(os.Stderr, timeline.RenderEvent(evt))
}

// JSONL output
Expand Down Expand Up @@ -364,10 +364,10 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
endTime := time.Now()

// Print summary stats
_, _ = _, _ = fmt.Fprintf(os.Stderr, "\n%s\n", correlator.Summary())
fmt.Fprintf(os.Stderr, "\n%s\n", correlator.Summary())

if readerErr != nil && ctx.Err() == nil {
_, _ = _, _ = fmt.Fprintf(os.Stderr, "⚠ Event reader error: %v\n", readerErr)
fmt.Fprintf(os.Stderr, "⚠ Event reader error: %v\n", readerErr)
}

// Write evidence bundle
Expand All @@ -390,7 +390,7 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
if err := bundle.Write(); err != nil {
return fmt.Errorf("failed to write evidence bundle: %w", err)
}
_, _ = _, _ = fmt.Fprintf(os.Stderr, "📁 Evidence bundle: %s/\n", opts.OutputDir)
fmt.Fprintf(os.Stderr, "📁 Evidence bundle: %s/\n", opts.OutputDir)
}

// Write standalone summary
Expand All @@ -404,7 +404,7 @@ func run(cmd *cobra.Command, args []string, opts *Options) error {
if err := sw.WriteToFile(opts.SummaryPath); err != nil {
return fmt.Errorf("failed to write summary: %w", err)
}
_, _ = _, _ = fmt.Fprintf(os.Stderr, "📝 Summary: %s\n", opts.SummaryPath)
fmt.Fprintf(os.Stderr, "📝 Summary: %s\n", opts.SummaryPath)
}

// Report exit code if we launched a process
Expand Down
9 changes: 9 additions & 0 deletions internal/cli/root_stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ func NewRootCommand() *cobra.Command {
},
}
}

// ExitError represents an error that should result in a specific exit code.
type ExitError struct {
Code int
}

func (e *ExitError) Error() string {
return fmt.Sprintf("exit status %d", e.Code)
}
Loading
Loading