New here? Open START_HERE.md for the one-command example.
TianoShield is a local, advisory-only research prototype for comparing a public upstream C patch with a sealed downstream file. It can:
- read immutable Git files or a verified source archive and patch series;
- classify a named downstream function relative to upstream pre-patch and post-patch functions;
- generate one narrowly eligible, inert whole-function patch candidate;
- replay that exact transformation and parse it in a disposable one-file copy; and
- emit canonical JSON and Markdown evidence for human review; and
- run a separate pinned build and regression-test recipe in disposable copies.
The installed commands are:
tianoshield-prepare-local
tianoshield-propagate run
tianoshield-propagate generate
tianoshield-propagate validate
tianoshield-propagate case
tianoshield-propagate packet
tianoshield-propagate source-package
tianoshield-propagate validate-build
case executes one requested case and retains each reached stage, including
withholding, abstention, timeout, and failed validation. packet produces a
review packet from that saved case. The tool stops at human review and performs
no external action. See Research status for the
complete prototype boundary and follow-up list.
The cross-project workflow covers the broader metadata inventory, archive preparation, package analysis, and bounded build/test evidence. Package analysis currently stops before sealed classifier issuance because that contract still requires Git target provenance.
Legend: Green = implemented and demonstrated. Orange = partially implemented or under evaluation. Black = human-controlled or planned. Solid arrows show the current evidence workflow; dashed arrows show planned, approval-gated steps.
UNCERTAINrequires human review and never means safe.NOT_APPLICABLEmeans only that the named function is absent from the supplied prepared file. A separate discovery record is required to describe a wider search.- High confidence establishes only exact normalized-text or normalized-AST correspondence for the sealed inputs.
- Structural, control-flow, data-flow, and Z3 results are evidence only. The production semantic-authority allowlist is empty.
- A candidate is untrusted and inert. Generation does not apply it or claim that it is correct.
- Isolated validation checks only exact transformation replay and Tree-sitter parsing. It does not preprocess, compile, link, test, execute, or statically analyze the result.
- No command contacts a vendor, pushes a branch, opens a pull request, publishes an artifact, merges a change, or releases software.
--public-saferemoves local paths and fixture-only labels. It is not a confidentiality scrubber.
The tool does not establish exploitability, severity, reachability, build inclusion, repository-wide remediation, patch correctness, maintainer acceptance, release readiness, or safety.
- CPython 3.11 through 3.13
uv- an existing complete local Git repository for repository preparation
- public, non-restricted input files
Install the locked environment and run the verification suite:
uv sync --frozen
uv run pytest -q
uv build
uv run tianoshield-propagate --versionThis demo creates a synthetic downstream Git repository, seals one committed C file, analyzes it, generates an inert candidate, and validates the candidate in a disposable copy. It does not use the network or modify any existing repository.
Run the commands from the TianoShield repository root after uv sync --frozen.
Use Python to select a canonical temporary path. This avoids symlinked
temporary-directory aliases such as /tmp on macOS, which the artifact
publishers correctly reject.
DEMO_ROOT="$(
uv run python - <<'PY'
import os
import tempfile
print(os.path.realpath(tempfile.mkdtemp(prefix="tianoshield-demo-")))
PY
)"
export DEMO_ROOT
mkdir -p \
"$DEMO_ROOT/downstream/Firmware" \
"$DEMO_ROOT/upstream" \
"$DEMO_ROOT/evidence/analysis"
cat >"$DEMO_ROOT/downstream/Firmware/Target.c" <<'EOF'
int target(int x) {
if (x > 10) {
return -1;
}
return 0;
}
EOF
cp \
"$DEMO_ROOT/downstream/Firmware/Target.c" \
"$DEMO_ROOT/upstream/Target.c"
git -C "$DEMO_ROOT/upstream" init -q
git -C "$DEMO_ROOT/upstream" config user.name 'TianoShield Demo'
git -C "$DEMO_ROOT/upstream" config user.email 'demo@example.invalid'
git -C "$DEMO_ROOT/upstream" add Target.c
git -C "$DEMO_ROOT/upstream" commit -q -m 'Add synthetic upstream pre-patch'
cat >"$DEMO_ROOT/upstream/Target.c" <<'EOF'
int target(int x) {
if (x >= 10) {
return -1;
}
return 0;
}
EOF
git -C "$DEMO_ROOT/upstream" add Target.c
git -C "$DEMO_ROOT/upstream" commit -q -m 'Fix synthetic upstream guard'
UPSTREAM_FIX="$(git -C "$DEMO_ROOT/upstream" rev-parse HEAD)"
export UPSTREAM_FIX
git -C "$DEMO_ROOT/downstream" init -q
git -C "$DEMO_ROOT/downstream" add Firmware/Target.c
git -C "$DEMO_ROOT/downstream" \
-c user.name='TianoShield Demo' \
-c user.email='demo@example.invalid' \
commit -q -m 'Add synthetic downstream target'
DOWNSTREAM_COMMIT="$(git -C "$DEMO_ROOT/downstream" rev-parse HEAD)"
export DOWNSTREAM_COMMITThe target matches upstream pre-patch bytes. The upstream post-patch file
changes x > 10 to x >= 10.
uv run tianoshield-prepare-local \
--repo "$DEMO_ROOT/downstream" \
--ref "$DOWNSTREAM_COMMIT" \
--repository-uri https://example.invalid/tianoshield/demo-fork.git \
--query-kind EXACT_PATH \
--query-value Firmware/Target.c \
--function target \
--upstream-repo "$DEMO_ROOT/upstream" \
--upstream-repository-uri \
https://example.invalid/tianoshield/demo-upstream.git \
--upstream-post-ref "$UPSTREAM_FIX" \
--upstream-path Target.c \
--patch-id DEMO-GUARD-OFF-BY-ONE \
--target-id demo-downstream \
--output "$DEMO_ROOT/evidence/prepared" \
--created-at 2026-08-12T12:00:00Z \
--public-inputsThe successful product contains:
prepared/
├── discovery-record.json
├── input-receipt.json
├── manifest.yaml
├── upstream-patch.json
└── prepared/
├── target/Firmware/Target.c
└── upstream/
├── pre.c
└── post.c
The receipt binds the manifest, upstream fix and immediate parent, whole source
files, downstream commit, source path, Git blob object, discovery record, target
ID, and all exact bytes. upstream-patch.json retains the commit and tree objects
needed to verify upstream file membership offline. Candidate bundles carry this
proof too. Repository URIs remain operator assertions; the proof does not verify
remote ownership, signatures, or a CVE assignment.
The fix must be a full lowercase commit ID in an existing local repository.
Its sole parent supplies the pre-patch input. For a merge fix, select an
immediate parent with --upstream-pre-ref. If the source was renamed, also set
--upstream-pre-path. Original bytes, including line endings, are preserved.
Loose upstream files are no longer accepted for repository preparation.
Preparation does not fetch, check out, invoke hooks, update refs, or read the mutable working-tree copy.
--public-inputs is an operator assertion. Do not use the current preparation
command for private, embargoed, credential-bearing, or license-restricted
material.
uv run tianoshield-propagate run \
"$DEMO_ROOT/evidence/prepared/manifest.yaml" \
--deterministic \
--json-output "$DEMO_ROOT/evidence/analysis/report.json" \
--write-markdown "$DEMO_ROOT/evidence/analysis/report.md" \
--write-advisory "$DEMO_ROOT/evidence/analysis/advisory.md" \
--no-colorThe synthetic target produces VULNERABLE with high confidence because its
normalized AST exactly matches upstream pre-patch. Tree-sitter supplies that
verdict. Structural and semantic observations remain evidence only.
The JSON output is always an array, even for one manifest. Inspect the compact
result without requiring jq:
uv run python - "$DEMO_ROOT/evidence/analysis/report.json" <<'PY'
import json
import sys
report = json.load(open(sys.argv[1], encoding="utf-8"))[0]
print(report["summary"])
for target in report["targets"]:
print(target["id"], target["verdict"], target["confidence"])
PYFor CI intake, add --fail-on vulnerable-or-uncertain. Exit 1 then means
analysis completed and selected a finding; it is not a runtime failure.
uv run tianoshield-propagate generate \
"$DEMO_ROOT/evidence/prepared/manifest.yaml" \
--target-id demo-downstream \
--output "$DEMO_ROOT/evidence/candidate" \
--created-at 2026-08-12T12:01:00ZInspect the candidate:
sed -n '1,120p' "$DEMO_ROOT/evidence/candidate/candidate.patch"Expected diff:
--- a/Firmware/Target.c
+++ b/Firmware/Target.c
@@ -1,5 +1,5 @@
int target(int x) {
- if (x > 10) {
+ if (x >= 10) {
return -1;
}
return 0;Generation requires more than high-confidence normalized correspondence. The raw downstream function bytes must exactly equal upstream pre-patch bytes. The generator retains the downstream file prefix and suffix, inserts the exact upstream post-patch function, and emits a single-file unified diff with four digest-bound derivation steps.
The output is a new atomic bundle containing the candidate, generated-patch record, classifier evidence, preparation provenance, source preimages, configuration, analyzer observations, and derivation descriptors. Its application, publication, and external-action authority fields are false.
An ineligible, unsupported, ambiguous, incomplete, timeout, or solver-unknown
case safely abstains with exit 1 and publishes no partial candidate bundle.
uv run tianoshield-propagate validate \
"$DEMO_ROOT/evidence/candidate" \
--output "$DEMO_ROOT/evidence/validation" \
--created-at 2026-08-12T12:02:00ZA successful demo reports:
APPLY_IN_DISPOSABLE_COPY PASS
PARSE PASS
aggregate status PASSED
The validator first replays every candidate preimage. It then creates a private tree containing only the prepared target, reconstructs and verifies the exact transformed bytes, parses them with the pinned Tree-sitter C grammar, removes the tree, and verifies cleanup before publishing evidence.
It never passes candidate bytes to Git, patch, a shell, or another
executable. Build, test, static analysis, execution, and repository mutation
are explicit exclusions.
The same prepared manifest can run through all stages with one request:
cat > "$DEMO_ROOT/evidence/case-request.json" <<'JSON'
{
"schema_version": "1.0",
"artifact_type": "case_request",
"case_id": "demo-case",
"target_id": "demo-downstream",
"mode": "PREPARED_MANIFEST",
"input": {"manifest": "prepared/manifest.yaml"},
"max_analysis_seconds": 60
}
JSON
uv run tianoshield-propagate case \
"$DEMO_ROOT/evidence/case-request.json" \
--output "$DEMO_ROOT/evidence/case"
uv run tianoshield-propagate packet \
"$DEMO_ROOT/evidence/case" \
--output "$DEMO_ROOT/evidence/review"Open review/REVIEW.md. The packet contains the saved case record, source
proofs, report, observations, candidate, and validation evidence for each stage
that ran. It also preserves reasons for stages that stopped. Packet creation
checks retained evidence without rerunning analyzers, generation, or validation.
The source repository is not needed to reopen a completed case. The review
shows the last reached stage, classifier observations, individual validation
checks, and whole-file versus named-function parsing limits. Syntax errors,
unsupported constructs, and incomplete coverage remain separate observations.
These are local evidence bundles. Requests and diagnostic logs can contain local paths; do not assume the entire bundle is redacted for publication. Byte integrity does not attest execution or establish that a patch is safe. Record the human review decision separately.
Use LOCAL_REPOSITORY mode to start with pinned Git inputs instead of a
prepared manifest. Its input.preparation object takes the local preparation
parameters with no output path. See the closed
request schema
and the September cohort.
Requests contain no review labels. Relative input paths resolve from the
request file's directory.
A maintainer reviews the evidence with the upstream advisory and patch.
The separate validate-build command can record bounded local build and test
evidence. Pull requests, merges, and releases remain external operations.
The temporary demo can be removed after review:
printf 'Demo root: %s\n' "$DEMO_ROOT"Delete only that exact displayed directory when its evidence is no longer needed.
The committed characterization fixtures cover all four verdicts:
uv run tianoshield-propagate run \
mock-supply-chain/stage2-downstream-benchmark/pixiefail/manifest.yaml \
mock-supply-chain/stage1.5-pixiefail/manifest.yaml \
--deterministic \
--no-colorThese fixtures demonstrate VULNERABLE, ALREADY_PATCHED, UNCERTAIN, and
NOT_APPLICABLE. They are a purposive research cohort, not an accuracy sample
for arbitrary downstream repositories.
Replay the label-blind classifier research evaluation:
uv run python scripts/evaluate-classifier-research.py --checkReplay the fixed dependent pipeline benchmark:
uv run python scripts/evaluate-dependent-benchmark.py --checkVerify the frozen benchmark packet through its research replay script:
uv run python scripts/build-operator-decision-packet.py --checkThis research projection retains its original identities. The installed
packet command consumes case bundles.
| Command | 0 |
1 |
2 |
3 |
|---|---|---|---|---|
run |
completed and policy passed | finding or expectation policy selected | usage, input, or output error | analysis/runtime failure |
generate |
complete inert bundle | safe abstention | invalid or unsafe input/output | analysis/runtime failure |
validate |
validation PASSED |
FAILED or INCOMPLETE evidence |
invalid, tampered, or unsafe input/output | runtime or unverified-cleanup failure |
case |
candidate validated | stopped dependency or failed/incomplete validation | invalid request or output | recorded input failure, timeout, or runtime error |
packet |
case review packet created | not used | invalid, tampered, collision, or unsafe input/output | publication/runtime or unverified-cleanup failure |
source-package |
package published | not used | invalid archive, patch, or output | runtime failure |
validate-build |
every step met its expectation | a step failed, timed out, or was unsupported | invalid recipe, source, or output | runtime failure |
An exit code controls the process only. It never grants authority or proves a patch safe.
- Preparation, candidate, validation, and packet outputs must be new paths.
- Candidate, validation, and installed packet output must be outside detected Git worktrees and bare repositories.
- Output parents must already exist and contain no symlink component.
- Publishers stage, reread, revalidate, and atomically rename complete bundles.
- Invalid input, unsafe output, and safe abstention leave no partial bundle.
- Cleanup uncertainty is a runtime failure and prevents evidence publication.
Runtime code is under src/tianoshield_propagate/. JSON Schemas ship inside
tianoshield_propagate.schemas; research fixtures and replay scripts are not
installed with the package.
- Architecture and evidence documents immutable inputs, analyzers, schemas, record identities, artifact transitions, and authority boundaries.
- Development and CI documents dependencies, validation, packaging, portability, and release prerequisites.
- Cross-project dataset workflow documents metadata selection, archive inputs, package analysis, and build/test evidence.
- SPIDER lineage compares TianoShield with the original SPIDER tool and records which capabilities were reimplemented.
- Research status identifies every fixed-fixture, benchmark, deferred, or otherwise non-production capability and lists the next iterations.
The removed Java/Joern, QAS, Python 2 statistics, and generated legacy output
trees are preserved at the Git tag spider-reference of the private working repository. They are not
runtime dependencies or compatibility targets.
The public repository
tianoshield-spider-safepatch-public
is a release mirror of the private working repository. Each public commit is an
export of a tagged working-repository commit, recorded in
SOURCE_PROVENANCE.json. Report issues on the public repository; maintainers
make changes in the working repository and publish them in the next release.
- Try the guided example
- Executed verification
- OpenSSF evidence and gaps
- Year 1 artifact index
- License and third-party notices
- Contributing and security reporting
TianoShield extends SPIDER, the safe-patch research of Dr. Aravind Machiry, a TianoShield project member, and colleagues:
Aravind Machiry, Nilo Redini, Eric Camellini, Christopher Kruegel, and Giovanni Vigna. "SPIDER: Enabling Fast Patch Propagation in Related Software Repositories." 2020 IEEE Symposium on Security and Privacy (SP), 2020.
The current Python implementation is a separate design. See SPIDER lineage for the comparison.