Skip to content

About

Stable Year 1 advisory C patch analysis, inert candidates, and isolated apply/parse validation.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TianoShield patch propagation

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:

  1. read immutable Git files or a verified source archive and patch series;
  2. classify a named downstream function relative to upstream pre-patch and post-patch functions;
  3. generate one narrowly eligible, inert whole-function patch candidate;
  4. replay that exact transformation and parse it in a disposable one-file copy; and
  5. emit canonical JSON and Markdown evidence for human review; and
  6. 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.

Patch-propagation workflow

Patch-propagation workflow from an upstream fix through evidence, human review, and planned patch validation

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.

Safety and interpretation

  • UNCERTAIN requires human review and never means safe.
  • NOT_APPLICABLE means 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-safe removes 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.

Requirements

  • 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 --version

Reproducible end-to-end demo

This 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.

1. Create public synthetic inputs

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_COMMIT

The target matches upstream pre-patch bytes. The upstream post-patch file changes x > 10 to x >= 10.

2. Prepare an immutable repository input

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-inputs

The 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.

3. Analyze patch state

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-color

The 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"])
PY

For CI intake, add --fail-on vulnerable-or-uncertain. Exit 1 then means analysis completed and selected a finding; it is not a runtime failure.

4. Generate an inert candidate

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:00Z

Inspect 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.

5. Validate in a disposable copy

uv run tianoshield-propagate validate \
  "$DEMO_ROOT/evidence/candidate" \
  --output "$DEMO_ROOT/evidence/validation" \
  --created-at 2026-08-12T12:02:00Z

A 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.

6. Run one complete case and create its review packet

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.

Exercise all classifier verdicts

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-color

These fixtures demonstrate VULNERABLE, ALREADY_PATCHED, UNCERTAIN, and NOT_APPLICABLE. They are a purposive research cohort, not an accuracy sample for arbitrary downstream repositories.

Research-only demonstrations

Replay the label-blind classifier research evaluation:

uv run python scripts/evaluate-classifier-research.py --check

Replay the fixed dependent pipeline benchmark:

uv run python scripts/evaluate-dependent-benchmark.py --check

Verify the frozen benchmark packet through its research replay script:

uv run python scripts/build-operator-decision-packet.py --check

This research projection retains its original identities. The installed packet command consumes case bundles.

Exit codes

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.

Output boundaries

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

Architecture and documentation

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.

Release and project information

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.

Origins

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.

About

Stable Year 1 advisory C patch analysis, inert candidates, and isolated apply/parse validation.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages