Configuration
Tien loads YAML from ~/.config/tien/config.yaml by default. Pass --config <path> to use a project-specific or CI-specific file. When a path is explicitly selected, a missing, unreadable, or invalid file fails the command or MCP startup; Tien does not silently fall back to defaults.
Defaults are safe enough to start: repository caching is on, URL caching and history are off, every non-local URL needs a scoped and expiring authorization, evidence is redacted, and engine mode is hybrid.
Complete example
repo:
engine_mode: "hybrid" # cli, docker, or hybrid
engines:
semgrep:
enabled: true
config: "p/default"
# 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-256-bit-challenge-for-this-host"
scope: "url-scan"
max_validity_minutes: 1440
output:
default_format: "gob"
redact_secrets_in_evidence: true
cache:
dir: "~/.cache/tien"
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"
command: "/absolute/path/to/check"
args: ["--format", "jsonl"]
timeout_ms: 20000
format: "jsonl"
enabled: false
Replace every <reviewed-version> placeholder with a reviewed immutable tag or image digest before using the example.
Engine modes
hybrid: use local scanners when installed and fall back to Docker.cli: require all enabled scanner commands inPATH.docker: always run enabled scanners in containers.
Disable an engine when it is not relevant or available. Semgrep and OSV-Scanner may need network access for rules or advisory data.
Exclusions
Exclusions reduce noise and scan time. Keep generated output, dependency folders, minified assets, fixtures containing fake credentials, and vendor code out of the scan when appropriate.
Do not exclude a folder only to make a finding disappear. Confirm why the folder is trusted and record the decision in version control.
Custom Semgrep rules
Tien includes a JS/TS-oriented rules pack:
repo:
engines:
semgrep:
enabled: true
config: "rules/semgrep/tien-js.yml"
When Tien is installed outside its source checkout, use an absolute path or another Semgrep-supported configuration reference.
Scoped URL authorization
Public URL targets always require an exact-host authorization. Generate one instead of inventing a reusable token:
tien authorize dns --domain app.example.com --valid-for 24h
For a multi-host batch, repeat --domain; the command emits one exact-host record and a distinct 256-bit challenge per domain. The generated hosts map is both an operator allowlist and the challenge lookup used for validation.
The command creates a 256-bit challenge and prints the operator config plus a TXT record similar to:
_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"
The record is valid only for the exact hostname, scope, challenge, and time window. An apex record does not authorize subdomains. max_validity_minutes is an operator policy, not a compiled scan cap, so a documented long-running engagement can raise it deliberately.
Tien accepts one authorization method: dns. The exact hostname must publish the structured TXT grant generated by tien authorize dns. Legacy http and dns+http configurations fail closed and must be migrated.
The url-scan scope authorizes only Tien's built-in GET/HEAD checks. Authorization is checked for the entire batch before workers, cache reads, or built-in requests run. One unauthorized target stops the whole batch. If the earliest grant expires during execution, Tien aborts with authorization failure; MCP returns a tool error rather than partial success. A batch containing any public target fails closed when a URL plugin is enabled. Credentials and fragments in target URLs are rejected, and redirects must stay on the same scheme, hostname, and effective port. Resolved loopback, link-local, CGNAT, multicast, unspecified, and metadata addresses remain blocked.
The DNS TXT grant is a cooperative operational signal, not legal or contractual authorization. Keep separate written permission and rules of engagement; a modified binary or fork can remove this check.
Localhost and literal private IPs are no longer exempt by default. Development environments can opt in with allow_localhost_without_proof or allow_private_ips_without_proof.
Allowing a domain name to resolve into RFC 1918 or IPv6 ULA space requires an exact operator allowlist entry, an https:// target, and the exact DNS TXT authorization grant:
safety:
allow_resolved_private_hosts:
- "internal.example.com"
proof:
method: "dns"
hosts:
internal.example.com: "GENERATED_HOST_SPECIFIC_256_BIT_CHALLENGE"
This is hostname-specific; it is not a wildcard or proof exemption. Tien pins the allowlisted private addresses during preflight and reuses those addresses for the scan. The legacy global allow_resolved_private_ips key is rejected by strict config parsing and must be migrated to allow_resolved_private_hosts.
Plugins
Plugins are external commands that emit normalized findings in JSONL or gob.
Repo plugins run with the scanned repository as their working directory. URL plugins receive the target list on stdin, but can currently run only for explicitly proof-exempt literal localhost or private-IP development targets. Public URL plugins need a future dedicated, origin-bound signed capability; url-scan does not authorize them. Tien provides context through environment variables including TIEN_PLUGIN_NAME, TIEN_PLUGIN_KIND, TIEN_SCAN_TIME, TIEN_REPO_PATH, and TIEN_TARGET_COUNT.
Treat plugin commands as trusted code: pin them, review updates, use timeouts, and avoid passing secrets in arguments.
Project-specific configuration
For repeatable human and agent use, commit a non-secret config such as .tien/config.yaml:
tien scan repo --config .tien/config.yaml --path . > .tien/current.tien
For MCP, keep the complete operator config outside agent-writable project files. Tien loads its fields once when the server starts, and neither scan tool has a config_path argument, so a tool call cannot replace authorization, engines, extra arguments, or plugin commands. Referenced rule files, plugin binaries, ignore files, and mutable image tags are not snapshotted: place them outside agent-writable roots where practical, protect their permissions, and pin versions or digests.