Output Formats

Trestle can report findings in various formats. Pick one with --output-format=<name> on the command line, or with output-format = "<name>" in a configuration file. The default is text.

The output formats supported by Trestle are:

  • text - Findings formatted for reading in a terminal, with optional ANSI colors.
  • csv - One finding per row, with a header row.
  • json - A single JSON document containing every finding.
  • junit - JUnit XML, suitable for CI systems that render JUnit reports.
  • sarif - SARIF, used by code-scanning dashboards such as GitHub's.
  • xml - A plain XML rendering of the findings.

Rule IDs

Every finding carries one of the following rule IDs. The ID identifies the kind of location the secret was found in, not the kind of credential.

  • secret-assignment - Secret assigned to a named variable, constant, parameter, or property.
  • secret-value - Secret value found in source code.
  • binary-secret - Secret found in a binary file.
  • text-secret - Secret found in a text file.
  • exposed-secret - Secret written to program output, where it may be exposed to users or other systems.

The rule ID value is the same in every format. How it is carried depends on the format:

  • csv writes it in the Rule ID column.
  • json writes it as the ruleId field on every diagnostic.
  • xml writes it as the ruleId attribute on every <diagnostic>.
  • junit writes it as the classname attribute on every <testcase>.
  • sarif writes it as ruleId on every result and lists all the rules under tool.driver.rules.
  • text does not include the rule ID. It writes the severity, location, and message only.

Severity

Every finding has one of two severities:

  • critical - the value can be identified as a credential from its own contents, without needing any other evidence.
  • warning - the value is consistent with a credential, but identifying it as one relied on surrounding context (the name it was assigned to, neighbouring code, or a heuristic).

How severity is rendered depends on the format:

  • text uppercases the value (CRITICAL, WARNING).
  • csv, json, and xml use lowercase.
  • junit uses lowercase inside the <failure type="..."> attribute.
  • sarif uses its own vocabulary instead: critical is written as error, and warning stays warning.

Assignment types

For findings with rule ID secret-assignment, every record carries an assignment type that names where in source code the secret was found:

  • argument - a value passed to a function, method, or macro call.
  • attribute - a value attached to a declaration through attribute syntax, such as a JSX element attribute.
  • backend configuration - a value inside a Terraform backend block, which does not permit variable interpolation.
  • build argument - a Dockerfile ARG declaration.
  • constant - a binding declared with the language's constant form.
  • directive - a configuration directive whose value follows the directive keyword as a bare token, as in a Redis configuration file.
  • element - the contents of a structural element rather than a named field, such as XML element text or an entry in a collection literal.
  • environment variable - a binding that defines a process environment variable, such as a Dockerfile ENV line or a dotenv entry.
  • header - an HTTP header value.
  • parameter - a parameter declared with a default value in a function, method, or block signature.
  • property - a named field inside a structured data record, such as a JSON, YAML, TOML, or XML key.
  • user - a credential bound to a named user account in an access control directive, such as a Redis ACL entry.
  • variable - a variable binding.

The assignment type appears as a structured field in every format that carries one:

  • csv writes it in the Assignment Type column.
  • json writes it as the assignmentType field.
  • xml writes it as the assignmentType attribute on the <diagnostic>.
  • junit writes it as the assignmentType attribute on the <testcase>.
  • sarif writes it under properties.assignmentType on every result.

The assignment type is also embedded in the message string of every format.

Locations

Source locations are absolute file paths. For secret-assignment and secret-value findings, the record also carries a start line, start column, end line, and end column. Lines and columns are one-based. For binary-secret and text-secret findings, the record carries only the file path, because the match covers the whole file.

Summary

When --show-summary is enabled (the default), every format appends scan statistics:

  • scannedFileCount - Number of files read.
  • elapsedMilliseconds - Wall-clock duration of the scan, in milliseconds.
  • criticalCount - Number of critical findings.
  • warningCount - Number of warning findings.
  • totalCount - Total finding count.
  • message - A human-readable summary line.

The summary is embedded in the document for json, sarif, xml, and junit. For text it is written after the findings on standard output. For csv it is written to standard error.

text

Findings are formatted for reading in a terminal. One line per finding, then an optional summary line.

WARNING src/api.ts:3:18 OpenAI key assigned to variable "apiKey".
CRITICAL keys/server.pem PEM private key found.
Scanned 12 files in 42ms. Found 1 secret and 1 warning.

csv

The format is one header row and one finding per row. The summary line, if enabled, is written to standard error.

The columns are, in order:

  1. Severity - the severity.
  2. Rule ID - the rule ID.
  3. Name - the identifier the secret was assigned to. Empty for rule IDs other than secret-assignment.
  4. Assignment Type - the assignment type. Empty for rule IDs other than secret-assignment.
  5. Description - a short label for the credential.
  6. Message - the human-readable finding message.
  7. File - absolute file path.
  8. Start Line,Start Column,End Line,End Column - the position of the match. Empty for binary-secret and text-secret.

When --explain is set, an Explanation column is appended.

Severity,Rule ID,Name,Assignment Type,Description,Message,File,Start Line,Start Column,End Line,End Column
warning,secret-assignment,apiKey,variable,OpenAI key,"OpenAI key assigned to variable ""apiKey"".",/repo/src/api.ts,3,18,3,68
critical,text-secret,,,PEM private key,PEM private key found.,/repo/keys/server.pem,,,,

json

A single JSON document with two top-level keys: diagnostics (an array of findings) and, when the summary is enabled, summary.

The fields on each diagnostic are:

  • ruleId - the rule ID.
  • severity - the severity.
  • name - the identifier the secret was assigned to. Omitted for rule IDs other than secret-assignment.
  • assignmentType - the assignment type. Omitted for rule IDs other than secret-assignment.
  • description - a short label for the credential.
  • message - the human-readable finding message.
  • file - absolute file path.
  • startLine,startColumn,endLine,endColumn - the position of the match. Omitted for binary-secret and text-secret.

When --explain is set, every diagnostic carries an additional explanation string.

{
  "diagnostics": [
    {
      "ruleId": "secret-assignment",
      "severity": "warning",
      "name": "apiKey",
      "assignmentType": "variable",
      "description": "OpenAI key",
      "message": "OpenAI key assigned to variable \"apiKey\".",
      "file": "/repo/src/api.ts",
      "startLine": 3,
      "startColumn": 18,
      "endLine": 3,
      "endColumn": 68
    }
  ],
  "summary": {
    "scannedFileCount": 12,
    "elapsedMilliseconds": 42,
    "criticalCount": 0,
    "warningCount": 1,
    "totalCount": 1,
    "message": "Scanned 12 files in 42ms. Found 1 warning."
  }
}

junit

JUnit XML, suitable for CI systems that render JUnit reports. The document has one <testsuite> with one <testcase> per finding.

On the <testsuite>:

  • name="trestle".
  • tests and failures both equal the total finding count.
  • errors="0".

On each <testcase>:

  • name - the source location (path:line:column) or the file path for whole-file findings.
  • classname - the rule ID.
  • assignmentType - the assignment type. Omitted for rule IDs other than secret-assignment.

Each <testcase> contains one <failure> with the severity as the type attribute, the finding message in the message attribute, and the rendered finding line as CDATA. When --explain is set, the remediation guidance is appended to the CDATA body.

When summary is enabled, a <properties> block lists the same summary fields documented above.

<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="trestle" tests="1" failures="1" errors="0">
  <properties>
    <property name="scannedFileCount" value="12"/>
    <property name="elapsedMilliseconds" value="42"/>
    <property name="criticalCount" value="0"/>
    <property name="warningCount" value="1"/>
    <property name="totalCount" value="1"/>
    <property name="message" value="Scanned 12 files in 42ms. Found 1 warning."/>
  </properties>
  <testcase name="/repo/src/api.ts:3:18" classname="secret-assignment" assignmentType="variable">
    <failure message="OpenAI key assigned to variable &quot;apiKey&quot;." type="warning">
      <![CDATA[
        /repo/src/api.ts:3:18 Warning: OpenAI key assigned to variable "apiKey".
      ]]>
    </failure>
  </testcase>
</testsuite>

sarif

SARIF 2.1.0. Suitable for any tool that consumes the OASIS SARIF specification, including GitHub's code-scanning dashboard.

The document has one runs entry with:

  • tool.driver.name = "trestle" and tool.driver.rules listing every rule ID with its description.
  • results - an array of findings. Each result carries ruleId (the rule ID), level (the severity in SARIF's vocabulary), message.text, and locations[].physicalLocation with the file URI and (for line-level findings) a region with start and end line and column. For secret-assignment findings, the result also carries the assignment type as properties.assignmentType.
  • properties.trestleSummary - the summary fields documented above, when summary is enabled.

When --explain is set, each result also carries message.markdown with the remediation guidance rendered as Markdown.

{
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
  "version": "2.1.0",
  "runs": [{
    "tool": {
      "driver": {
        "name": "trestle",
        "rules": [
          { "id": "secret-assignment", "shortDescription": { "text": "..." } },
          { "id": "secret-value",      "shortDescription": { "text": "..." } },
          { "id": "binary-secret",     "shortDescription": { "text": "..." } },
          { "id": "text-secret",       "shortDescription": { "text": "..." } }
        ]
      }
    },
    "results": [
      {
        "ruleId": "secret-assignment",
        "level": "warning",
        "message": { "text": "OpenAI key assigned to variable \"apiKey\"." },
        "locations": [{
          "physicalLocation": {
            "artifactLocation": { "uri": "file:///repo/src/api.ts" },
            "region": { "startLine": 3, "startColumn": 18, "endLine": 3, "endColumn": 68 }
          }
        }],
        "properties": { "assignmentType": "variable" }
      }
    ],
    "properties": {
      "trestleSummary": {
        "scannedFileCount": 12,
        "elapsedMilliseconds": 42,
        "criticalCount": 0,
        "warningCount": 1,
        "totalCount": 1,
        "message": "Scanned 12 files in 42ms. Found 1 warning."
      }
    }
  }]
}

xml

A plain XML rendering of the findings, with no external schema. The root element is <trestle>, containing <diagnostics> and (when summary is enabled) <summary>.

The attributes on each <diagnostic> are:

  • ruleId - the rule ID.
  • severity - the severity.
  • name - the identifier the secret was assigned to. Omitted for rule IDs other than secret-assignment.
  • assignmentType - the assignment type. Omitted for rule IDs other than secret-assignment.
  • description - a short label for the credential.
  • file - absolute file path.
  • startLine,startColumn,endLine,endColumn - the position of the match. Omitted for binary-secret and text-secret.

The element body holds a <message> child with CDATA. With --explain, an <explanation> child is added alongside the message.

The <summary> element holds one child per summary field. The <message> child wraps its text in CDATA; the other fields hold their value as plain text.

<?xml version="1.0" encoding="UTF-8"?>
<trestle>
  <diagnostics>
    <diagnostic ruleId="secret-assignment" severity="warning" name="apiKey" assignmentType="variable" description="OpenAI key" file="/repo/src/api.ts" startLine="3" startColumn="18" endLine="3" endColumn="68">
      <message><![CDATA[OpenAI key assigned to variable "apiKey".]]></message>
    </diagnostic>
  </diagnostics>
  <summary>
    <scannedFileCount>12</scannedFileCount>
    <elapsedMilliseconds>42</elapsedMilliseconds>
    <criticalCount>0</criticalCount>
    <warningCount>1</warningCount>
    <totalCount>1</totalCount>
    <message><![CDATA[Scanned 12 files in 42ms. Found 1 warning.]]></message>
  </summary>
</trestle>

Picking a format

  • text for interactive runs and pre-commit output.
  • sarif when the destination is a code-scanning dashboard or anything that reads SARIF.
  • junit when CI is the consumer and the goal is to surface findings as test failures.
  • json for ad-hoc scripts and post-processing.
  • csv for spreadsheets and quick filtering.
  • xml for downstream tools that prefer plain XML over SARIF.

See Command Line for the --output-format, --output-file, and --explain options that control where the formatted output goes and whether it carries remediation guidance.