Repository: https://github.com/seibel777/tien.app
Tien is a local-first CLI that runs 100% passive security checks on:
- Repositories (JS/TS-focused) via SAST/SCA/secrets tooling
- URLs via non-invasive HTTP/TLS/header analysis (GET/HEAD only)
It’s inspired by “pipeline-style” CLIs (scan → encode/merge → report/plot): the scanner emits results, and other commands consume those results to generate reports or visualizations.
Tien does not attempt exploitation. Passive findings are signals + evidence, not proof of compromise.
- Single binary (local usage)
- Passive-only URL checks (no payloads, fuzzing, brute force)
- Repo + URL from day one
- Stream-friendly workflows (stdin/stdout, file outputs)
- Deterministic output (easy diff between runs)
- Scales locally (concurrency, caching, shard mode)
- Secrets detection (tokens/keys committed by mistake)
- Dependencies vulnerability scanning (SCA)
- Static rules for insecure patterns (SAST), with a JS/TS focus
- Normalizes results into a single finding format
Only GET/HEAD and analysis:
- TLS certificate / HTTPS posture
- Security headers (CSP/HSTS/etc.)
- Cookie flags (Secure/HttpOnly/SameSite) when present
- CORS misconfig patterns
/.well-known/security.txtpresence- Mixed content signals (http assets referenced by https pages)
- No active exploitation
- No credential guessing / brute force
- No payload injection testing (XSS/SQLi/etc.)
- No aggressive crawling or wordlists
If you want active testing, use dedicated tools. Tien stays passive by design.
- Implemented: config loader, Finding schema, gob/jsonl IO, merge, encode, reports (text/md/sarif/json), HTML plot, diff, URL scan (TLS + headers + cookies + CORS + security.txt + mixed content), host summary, repo scan (gitleaks + osv-scanner + semgrep), caching (repo + optional URL), run history, shard mode, retry + per-target timeout, graceful cancellation, plugin system, policy mode, Prometheus metrics, minimal TUI summary, custom Semgrep rules pack, hybrid gitleaks runner (CLI or Docker fallback).
- Go (CLI + pipeline + storage + reports)
Modes:
- hybrid (default): use local CLIs when available, fallback to Docker with a warning.
- cli: require installed CLIs.
- docker: always run tools in containers.
Repo scan integrates gitleaks, osv-scanner, and semgrep.
gob(default; compact + fast)jsonl(debug + easy integrations)textreportmdreportsarifreportjsonreporthtmlplot
go install github.com/seibel777/tien.app/cmd/tien@latest
tien --helpbrew tap seibel777/tien.app https://github.com/seibel777/tien.app
brew install tienRequires the tap formula updated with the latest release checksums (see docs/site.md).
npm i -g @seibel777/tien
tien --helpRequires the npm package to be published (see packages/npm).
git clone https://github.com/seibel777/tien.app.git
cd tien.app
go build -o tien ./cmd/tien
./tien --helpPrebuilt binaries: https://github.com/seibel777/tien.app/releases
Linux amd64 example:
curl -L -o tien https://github.com/seibel777/tien.app/releases/latest/download/tien_linux_amd64
chmod +x tien
./tien --helpmacOS arm64 example:
curl -L -o tien https://github.com/seibel777/tien.app/releases/latest/download/tien_darwin_arm64
chmod +x tien
./tien --helpChecksums are published as *.sha256 next to each binary.
Option A - installed CLIs Ensure these commands are available in $PATH:
- gitleaks
- semgrep
- osv-scanner
Option B - Docker Install Docker and enable the "docker engine" mode in config (see below).
Don’t mix A and B unless you enjoy environment debugging.
URL scan (targets via stdin)
cat targets.txt | tien scan url --format gob > url.tienURL scan (targets file)
tien scan url --targets targets.txt --format jsonl > url.jsonlHost summary is printed to stderr by default; disable with --summary=false.
URL scan (shard mode)
tien scan url --targets targets.txt --shard 1/5 --format gob > url_shard_1.tienURL scan with policy mode
tien scan url --targets targets.txt --policy high --format gob > url_policy.tienURL scan with Prometheus metrics
tien scan url --targets targets.txt --metrics true --metrics-listen 127.0.0.1:9465 --format gob > url.tienMerge results
tien merge url.tien other.tien > all.tienReport (text)
tien report --type text all.tienReport (Markdown)
tien report --type md all.tien > report.mdReport (SARIF)
tien report --type sarif all.tien > tien.sarifPlot (HTML)
tien plot all.tien > report.htmlEncode (JSONL)
tien encode --to jsonl all.tien > all.jsonlDiff scans
tien diff old.tien new.tienHistory (local run index)
tien history --limit 10Enable history in config or pass --history true to record runs.
Repo scan (gitleaks + osv-scanner + semgrep)
tien scan repo --path . --format gob > repo.tienNote: osv-scanner and semgrep may require network access for advisories or rules; disable them in config if needed.
Repo scans are cached by commit/config/engine versions by default; disable with --cache false.
URL cache is optional (ETag/Last-Modified) and disabled by default.
Hybrid gitleaks (CLI or Docker fallback)
tien hybrid --path . > gitleaks.jsonOutputs raw gitleaks JSON (not normalized Findings yet).
Note: gitleaks returns exit code 1 when leaks are found; tien hybrid treats that as success so you still get output.
Note: URL scanning requires ownership proof by default. Configure safety.proof in your config or disable the requirement explicitly for trusted scopes.
targets.txt (one per line):
https://example.com https://app.example.com https://api.example.com
You can also pipe from another tool:
some-generator | tien scan url > url.tien
Default path: • macOS/Linux: ~/.config/tien/config.yaml
Example:
repo: engine_mode: "hybrid" # "cli", "docker", or "hybrid" engines: semgrep: enabled: true config: "p/default" # or rules/semgrep/tien-js.yml max_target_bytes: 2000000 image: "returntocorp/semgrep:latest" gitleaks: enabled: true image: "ghcr.io/gitleaks/gitleaks:latest" osv: enabled: true image: "ghcr.io/google/osv-scanner:latest" exclude: - "node_modules/" - "dist/" - ".next/" - "build/" - "**/*.min.js"
url: concurrency: 20 timeout_ms: 8000 max_target_ms: 15000 max_body_kb: 256 follow_redirects: true retry: max_attempts: 2 backoff_ms: 200 checks: tls: true headers: true cookies: true cors: true securitytxt: true mixed_content: true
safety: require_ownership_proof: true allow_localhost_without_proof: true allow_private_ips_without_proof: true proof: method: "http" # "http" or "dns" token: "YOUR_RANDOM_TOKEN"
output: default_format: "gob" redact_secrets_in_evidence: true
cache: dir: "~/.cache/tien" # optional override repo: enabled: true url: enabled: false history: enabled: false
policy: enabled: false min_severity: "high"
metrics: enabled: false listen: "127.0.0.1:9465"
plugins:
- name: "custom-check" kind: "repo" # or "url" command: "/path/to/plugin" args: ["--flag"] format: "jsonl" timeout_ms: 20000 enabled: false
Plugins let you add custom checks without forking Tien. Each plugin is an external command that outputs Findings in JSONL or gob.
Plugin environment variables:
• TIEN_PLUGIN_NAME
• TIEN_PLUGIN_KIND (repo|url)
• TIEN_SCAN_TIME (RFC3339)
• TIEN_REPO_PATH (repo plugins)
• TIEN_TARGETS_FORMAT=lines + TIEN_TARGET_COUNT (url plugins)
URL plugins receive the targets list on stdin (one URL per line). Repo plugins run with the repo path as working directory.
Use the bundled rules pack in rules/semgrep/tien-js.yml for JS/TS-focused checks. Set:
repo:
engines:
semgrep:
config: "rules/semgrep/tien-js.yml"- Prometheus metrics:
tien scan url --metrics true --metrics-listen 127.0.0.1:9465exposes/metrics. - Policy mode:
tien scan repo --policy highexits non-zero if findings >= severity. - Minimal TUI: add
--tuito show a live summary on stderr.
By default, URL scanning requires proof that you control the target to reduce accidental scanning.
Supported methods (recommended): • HTTP: /.well-known/tien-verification must return a token • DNS TXT: tien= on the domain
Config example:
safety: require_ownership_proof: true proof: method: "http" # "http" or "dns" token: "YOUR_RANDOM_TOKEN"
Notes: • localhost and private IPs can be exempted in config • You can disable proof requirement, but that’s on you
Pipeline model
Tien follows a two-step model: 1. scan commands emit a stream of findings (stdout/file) 2. report/plot/encode/merge consume findings and produce outputs
This makes it easy to: • run scans on multiple machines • merge outputs later • keep scanning logic separate from reporting/visualization
Finding schema (single format for repo + url)
Each issue is normalized into a Finding: • id: stable check identifier (url.headers.hsts_missing, repo.secrets.generic, repo.deps.osv) • source: url or repo • target: URL or path:line • severity: info|low|med|high|crit • confidence: low|med|high • title: short human title • description: what was detected and why it matters • evidence: short, redacted proof (headers/snippet/package) • remediation: direct fix guidance • tags: e.g. cwe:, owasp:, category:* • fingerprint: deterministic hash to deduplicate across runs • timestamp: when detected • run_id: run identifier (for comparisons)
Fingerprint rules (must be stable) • URL findings: hash of (id + normalized_host + normalized_path? + key_evidence_fields) • Repo findings: hash of (id + file_path + line + rule_id/package_id)
This enables: • deduplication • diff between runs • stable reporting even with concurrency
See docs/checks.md for the full list of check IDs.
Engines
Repo engines implement: • run external tool • parse tool output (JSON/SARIF where possible) • normalize into Finding
URL checks implement: • fetch (GET/HEAD only) with timeouts and limits • analyze TLS/headers/body (bounded) • emit normalized Finding
Commands overview
tien scan repo
Scans a repo path using configured engines and emits findings. Runs the enabled engines (gitleaks, osv-scanner, semgrep). Supports commit-based caching when enabled.
Common flags: • --path
• --format gob|jsonl • --config • --cache auto|true|false • --history auto|true|false • --policy <severity|off> • --metrics auto|true|false • --metrics-listen • --tuitien scan url
Reads URLs from stdin (or --targets) and emits findings.
Common flags: • --targets (optional; stdin by default) • --format gob|jsonl • --summary=true|false • --config • --shard N/M • --cache auto|true|false • --history auto|true|false • --policy <severity|off> • --metrics auto|true|false • --metrics-listen • --tui
Config options control concurrency, per-target runtime, retries, redirects, and ownership proof.
tien hybrid
Runs gitleaks using the local CLI or Docker fallback and emits raw JSON.
Common flags: • --path
• --imagetien merge
Merges multiple .tien files into one.
tien report
Generates reports: • --type text|md|sarif|json • --min-severity info|low|med|high|crit • --group-by source|severity|target|none • --only-tags tag1,tag2 • --top
tien encode
Converts formats: • --to gob|jsonl
tien plot
Generates report.html from findings. Common flags: • --min-severity info|low|med|high|crit • --only-tags tag1,tag2
tien diff
Compares two scans and reports new/fixed/changed findings.
tien history
Shows the local run history (when enabled in config).
Common flags: • --format text|json • --limit
See roadmap.md for the phased checklist and optional extensions.
Testing guide: docs/testing.md.
Project layout
tien/ cmd/tien/ # CLI entrypoint internal/app/ # config + wiring internal/cache/ # repo/url caches + history storage internal/history/ # local run history helpers internal/metrics/ # Prometheus metrics collector internal/plugins/ # plugin runner for custom checks internal/scan/ # orchestration + URL/repo scanning internal/engines/repo/ # semgrep/osv/gitleaks wrappers internal/diff/ # scan diffing internal/store/ # gob/jsonl IO + merge internal/report/ # text/md/sarif/json internal/plot/ # html plot pkg/model/ # exported Finding schema pkg/version/ docs/ examples/ rules/semgrep/ .github/workflows/
License
Apache-2.0. See LICENSE.
Links
- Repo: https://github.com/seibel777/tien.app
- Releases: https://github.com/seibel777/tien.app/releases
- Issues: https://github.com/seibel777/tien.app/issues
- Docs:
docs/README.md - Flags:
tien --help - Changelog:
CHANGELOG.md - License:
LICENSE - Security:
SECURITY.md - Contributing:
CONTRIBUTING.md
Disclaimer
Tien is intended for scanning: • code you own • systems you are authorized to assess
Passive scanning reduces risk, but it is still your responsibility to comply with laws, policies, and scope restrictions.