Content
# Tien
[](https://github.com/seibel777/tien.app/actions/workflows/ci.yml)
Repository: https://github.com/seibel777/tien.app
**Tien** is a local-first security CLI that helps people and AI coding agents turn AI-built or vibe-coded apps into reviewable evidence. Its **built-in checks are passive** 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.
New to security tooling? Start with [the getting-started guide](docs/getting-started.md). Using Codex, Claude Code, Cursor, Copilot, or another coding agent? Use [the AI agent integration guide](docs/ai-agents.md).
---
## Goals
- **Single binary** (local usage)
- **Passive-only built-in** 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)
---
## What Tien does
### Repo scan (passive)
- **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
### URL scan (passive)
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.txt` presence
- Mixed content signals (http assets referenced by https pages)
---
## Behavioral safety boundaries
- No active exploitation
- No credential guessing / brute force
- No payload injection testing (XSS/SQLi/etc.)
- No aggressive crawling or wordlists
Tien does not provide active testing or penetration-testing behavior. Its built-in checks stay passive by design. External plugins are operator-trusted processes and are not sandboxed; review them and enforce read-only filesystem and network policy separately.
Tien does not impose arbitrary caps on target count, finding count, or repository file size. Configurable timeouts, response-body size, redirects, retries, and concurrency remain resource budgets so a scan cannot consume unbounded local or remote resources.
---
## Status (current)
- 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).
---
## Stack
### Core
- Go (CLI + pipeline + storage + reports)
### Engines (invoked by Tien)
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.
### Output formats (current)
- `gob` (default; compact + fast)
- `jsonl` (debug + easy integrations)
- `text` report
- `md` report
- `sarif` report
- `json` report
- `html` plot
---
## Install
### Go install
```bash
go install github.com/seibel777/tien.app/cmd/tien@latest
tien --help
```
### Homebrew
```bash
brew tap seibel777/tien.app https://github.com/seibel777/tien.app
brew install tien
```
Requires the tap formula updated with the latest release checksums. Check the [public installation guide](https://tienapp.com/docs/getting-started) for the currently supported paths.
### NPM
```bash
npm i -g @seibel777/tien
tien --help
```
Requires the npm package to be published (see `packages/npm`).
### GitHub (clone)
```bash
git clone https://github.com/seibel777/tien.app.git
cd tien.app
go build -o tien ./cmd/tien
./tien --help
```
### Releases (binary download)
Prebuilt binaries: https://github.com/seibel777/tien.app/releases
Linux amd64 example:
```bash
curl -L -o tien https://github.com/seibel777/tien.app/releases/latest/download/tien_linux_amd64
chmod +x tien
./tien --help
```
macOS arm64 example:
```bash
curl -L -o tien https://github.com/seibel777/tien.app/releases/latest/download/tien_darwin_arm64
chmod +x tien
./tien --help
```
Checksums are published as `*.sha256` next to each binary.
### Engine dependencies (repo scan)
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.
---
## Usage (current)
URL scan (targets via stdin)
```bash
cat targets.txt | tien scan url --format gob > url.tien
```
URL scan (targets file)
```bash
tien scan url --targets targets.txt --format jsonl > url.jsonl
```
Host summary is printed to stderr by default; disable with `--summary=false`.
URL scan (shard mode)
```bash
tien scan url --targets targets.txt --shard 1/5 --format gob > url_shard_1.tien
```
URL scan with policy mode
```bash
tien scan url --targets targets.txt --policy high --format gob > url_policy.tien
```
URL scan with Prometheus metrics
```bash
tien scan url --targets targets.txt --metrics true --metrics-listen 127.0.0.1:9465 --format gob > url.tien
```
Merge results
```bash
tien merge url.tien other.tien > all.tien
```
Report (text)
```bash
tien report --type text all.tien
```
Report (Markdown)
```bash
tien report --type md all.tien > report.md
```
Report (SARIF)
```bash
tien report --type sarif all.tien > tien.sarif
```
Plot (HTML)
```bash
tien plot all.tien > report.html
```
Encode (JSONL)
```bash
tien encode --to jsonl all.tien > all.jsonl
```
Diff scans
```bash
tien diff old.tien new.tien
```
History (local run index)
```bash
tien history --limit 10
```
Enable history in config or pass `--history true` to record runs.
Repo scan (gitleaks + osv-scanner + semgrep)
```bash
tien scan repo --path . --format gob > repo.tien
```
Note: 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)
```bash
tien hybrid --path . > gitleaks.json
```
Outputs 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: every public URL target requires a scoped, expiring authorization for its exact hostname. Generate the DNS TXT grant with `tien authorize dns`; public-target authorization cannot be disabled by a scan caller.
---
## Targets format (URL scan)
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
---
## Configuration
Default path:
- macOS/Linux: `~/.config/tien/config.yaml`
When you explicitly pass `--config <path>`, that file is required. A missing, unreadable, or invalid selected file fails the command instead of falling back to the default path or built-in defaults.
Example:
```yaml
repo:
engine_mode: "hybrid" # "cli", "docker", or "hybrid"
engines:
semgrep:
enabled: true
config: "p/default" # or rules/semgrep/tien-js.yml
# max_target_bytes is optional; Tien passes 0 to disable Semgrep's implicit file-size filter
# timeout_seconds and timeout_threshold are optional; 0 disables implicit skip thresholds
image: "semgrep/semgrep:<reviewed-version>" # or an immutable @sha256 digest
gitleaks:
enabled: true
image: "ghcr.io/gitleaks/gitleaks:<reviewed-version>"
osv:
enabled: true
image: "ghcr.io/google/osv-scanner:<reviewed-version>"
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
max_redirects: 5
retry:
max_attempts: 2
backoff_ms: 200
checks:
tls: true
headers: true
cookies: true
cors: true
securitytxt: true
mixed_content: true
safety:
allow_localhost_without_proof: false
allow_private_ips_without_proof: false
allow_resolved_private_hosts: [] # exact hosts only; private answers require HTTPS + DNS TXT authorization
proof:
method: "dns" # DNS TXT is the only supported authorization method
hosts:
app.example.com: "GENERATED_HOST_SPECIFIC_256_BIT_CHALLENGE"
scope: "url-scan"
max_validity_minutes: 1440
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
```
Replace every `<reviewed-version>` placeholder with a reviewed immutable tag or image digest before using the example.
---
## Plugins (custom checks)
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)
Repo plugins run with the repo path as their working directory. URL plugins receive the targets list on stdin (one URL per line), but the current `url-scan` authorization scope covers built-in `GET`/`HEAD` checks only. A batch containing any public target fails closed when a URL plugin is enabled. URL plugins can currently run only for explicitly proof-exempt literal localhost or private-IP development targets.
---
## Custom Semgrep rules pack
Use the bundled rules pack in `rules/semgrep/tien-js.yml` for JS/TS-focused checks. Set:
```yaml
repo:
engines:
semgrep:
config: "rules/semgrep/tien-js.yml"
```
---
## Metrics, policy, and TUI
- **Prometheus metrics**: `tien scan url --metrics true --metrics-listen 127.0.0.1:9465` exposes `/metrics`.
- **Policy mode**: `tien scan repo --policy high` exits non-zero if findings >= severity.
- **Minimal TUI**: add `--tui` to show a live summary on stderr.
---
## Scoped authorization (URL scan safety)
Every public URL target requires authorization for its exact hostname. Create a 256-bit challenge and an expiring grant instead of reusing a permanent token:
```bash
tien authorize dns --domain app.example.com --valid-for 24h
```
Repeat `--domain` in one command for a multi-host batch; Tien emits one exact-host grant and a distinct challenge per domain, forming the operator's explicit host allowlist.
Publish the structured value at the DNS name printed by the command, for example:
```text
_tien-authorization.app.example.com. TXT "v=tien1;host=app.example.com;scope=url-scan;challenge=...;issued=2026-07-15T12:00:00Z;expires=2026-07-16T12:00:00Z"
```
An authorization for `example.com` does not authorize `app.example.com`. Tien validates the hostname, `url-scan` scope, challenge, issue time, expiry, and the operator-configured maximum validity.
Config example:
```yaml
safety:
allow_localhost_without_proof: false
allow_private_ips_without_proof: false
allow_resolved_private_hosts: [] # private DNS answers require HTTPS + DNS TXT authorization
proof:
method: "dns"
hosts:
app.example.com: "GENERATED_HOST_SPECIFIC_256_BIT_CHALLENGE"
scope: "url-scan"
max_validity_minutes: 1440
```
DNS TXT is Tien's only URL authorization method. Legacy `http` and `dns+http` configurations fail closed and must be migrated to `method: "dns"`.
The `url-scan` grant is a cooperative operational scope signal for Tien's built-in `GET`/`HEAD` checks. It is not legal or contractual authorization, does not replace written permission or rules of engagement, and cannot stop a modified binary or fork from removing the guardrail.
Tien authorizes the complete target batch before starting workers, cache reads, or built-in network requests. One unauthorized target stops the complete batch. If the earliest grant expires during execution, Tien aborts the run and reports authorization failure; MCP returns a tool error, not partial success. Public URL plugins fail closed because they are outside the `url-scan` capability; only explicitly proof-exempt literal localhost or private-IP targets can currently reach a URL plugin. Redirects must remain on the same scheme, hostname, and effective port. A hostname that resolves to RFC 1918 or IPv6 ULA addresses must appear exactly in `allow_resolved_private_hosts`, use an `https://` target, and present its exact DNS TXT grant; resolved addresses are pinned for the run. Loopback through a non-local name, link-local, CGNAT, multicast, unspecified, and metadata addresses remain blocked. Local and private development targets require explicit operator exemptions.
---
## Design
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 mcp
Runs a local Model Context Protocol server over stdin/stdout so compatible coding agents can invoke Tien with typed inputs and structured outputs.
Common flags:
- `--root <dir>` (repository boundary; defaults to current directory)
- `--config <path>` (operator-owned policy fields loaded when the server starts; a missing selected path fails startup)
The server exposes capabilities, repository scanning, URL scanning, and artifact diff tools. Paths cannot escape the selected root, evidence is excluded by default, and the operator-owned policy fields for engines, plugins, arguments, and URL authorization are loaded once at startup. An explicitly selected missing, unreadable, or invalid `--config` path fails startup instead of falling back to defaults. Neither scan tool accepts agent-controlled replacement config. Referenced rule files, plugin binaries, ignore files, and mutable image tags are not snapshotted and can drift independently; protect them from agent writes and pin versions or digests. Every URL batch fails closed unless the startup policy authorizes all exact hosts, and public URL plugins remain outside the `url-scan` scope. MCP imposes no target-count or finding-count ceiling; a caller may still request a smaller presentation with the optional `max_findings` tool argument.
See `docs/ai-agents.md` for client configuration and the agent operating contract.
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 <dir>
• --format gob|jsonl
• --config <path>
• --cache auto|true|false
• --history auto|true|false
• --policy <severity|off>
• --metrics auto|true|false
• --metrics-listen <addr>
• --tui
tien scan url
Reads URLs from stdin (or --targets) and emits findings.
Common flags:
• --targets <file> (optional; stdin by default)
• --format gob|jsonl
• --summary=true|false
• --config <path>
• --shard N/M
• --cache auto|true|false
• --history auto|true|false
• --policy <severity|off>
• --metrics auto|true|false
• --metrics-listen <addr>
• --tui
Config options control concurrency, per-target runtime, response-body size, retries, redirects, and scoped authorization. These resource budgets do not cap target or finding counts.
tien hybrid
Runs gitleaks using the local CLI or Docker fallback and emits raw JSON.
Common flags:
• --path <dir>
• --image <docker-image>
tien 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 <n>
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 <n>
---
## Roadmap
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/mcpserver/ # local MCP tools + repository boundary enforcement
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 obtain written authorization and comply with laws, policies, rules of engagement, and scope restrictions. Tien's DNS TXT proof is a cooperative operational guardrail, not legal or contractual permission.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available