TIENDocumentation
Docs/Reference

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 in PATH.
  • 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.