Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 17 additions & 13 deletions crates/tirith-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ artifact-hash-lookup = []
test-network-seams = []
# native-yara (yara-x): DEFERRED by the PR-0 dependency spike, NOT wired here.
# The plan pencilled in `native-yara = ["dep:yara-x"]` with yara-x 0.14, but on the
# current registry yara-x 0.14 requires `intaglio >= 1.10`, and intaglio 1.10 (its
# lowest available release) bumped its MSRV to Rust 1.85. yara-x 0.14 also pulls
# `ar_archive_writer` (Rust 1.88) and a newer `time` (1.88) transitively. There is
# no version of the yara-x 0.14 closure that builds on our MSRV 1.83, so the
# feature and the `yara-x` dependency are intentionally omitted. The engine already
# treats an absent native-yara feature as `native_yara_rules: unavailable` (not
# "incomplete"), so this is a clean no-op. Revisit when the workspace MSRV rises to
# >= 1.85 or a yara-x line with a 1.83-compatible closure ships.
# original dependency spike ran while the workspace MSRV was 1.83 and found the
# yara-x 0.14 closure required newer Rust. The workspace is now on 1.88, but this
# optional engine remains deliberately unwired until its operational and policy
# contract is reviewed as a separate change. The engine treats an absent
# native-yara feature as `native_yara_rules: unavailable` (not "incomplete"), so
# this remains an honest no-op rather than silently claiming coverage.

[dependencies]
url = { workspace = true }
Expand Down Expand Up @@ -66,13 +64,17 @@ reqwest = { version = "0.12", default-features = false, features = ["blocking",
csv = { workspace = true }
owo-colors = { workspace = true }
zip = { version = "2", default-features = false, features = ["deflate"] }
# repo-0332: flate-decode PDF compressed object streams for the nesting
# preflight (lopdf 0.34 cannot be trusted to parse them safely). Same version
# already in the lockfile via `zip`.
flate2 = "1"
# B7 native binary triage. A vetted, pure-Rust, READ-ONLY object-format parser
# (gimli-rs/object), used to read ELF / fat (universal) Mach-O / PE+TLS section,
# symbol, import and init tables from a bounded in-memory buffer. Features are
# minimized to exactly the formats we triage: no `write`/`build` (we never emit
# objects), no `compression`/`archive`/`wasm`/`xcoff`. `pe` pulls `coff`. We
# enable `std` for the borrowed-data `read` API and `std::io::Error`. MSRV: the
# crate declares rust-version 1.65, under our workspace MSRV 1.83. Reviewed under
# crate declares rust-version 1.65, below our workspace MSRV 1.88. Reviewed under
# the repo `cargo deny` gate (Apache-2.0 OR MIT, both allowlisted). This replaces
# hand-rolling ELF + fat Mach-O + PE/TLS parsing, which is a far larger
# malformed-input attack surface; `object` is widely fuzzed and read-only.
Expand All @@ -92,8 +94,9 @@ getrandom = "0.3"
# because it pulls tokio + Apache-only). `default-features = false` plus the single
# protocol-revision feature keeps the surface to serde structs we map known
# content onto, while unknown blocks are preserved losslessly by our own
# `PreservedContent::Unknown(serde_json::Value)` wrapper. Proven on MSRV 1.83 in
# the PR-0 dependency spike (crate MSRV 1.80). License MIT.
# `PreservedContent::Unknown(serde_json::Value)` wrapper. Proven on Rust 1.83 in
# the PR-0 dependency spike (crate MSRV 1.80), below the current 1.88 workspace
# floor. License MIT.
rust-mcp-schema = { version = "0.10", default-features = false, features = ["2025_11_25"] }
# JSON Schema validation for MCP inputSchema -> args and outputSchema ->
# result.structuredContent (Stack C). `default-features = false` is mandatory:
Expand Down Expand Up @@ -149,8 +152,9 @@ windows = { version = "0.62", default-features = false, features = [
# extrasafe = an ergonomic seccomp-BPF policy builder (wraps seccompiler). Both
# are applied by the internal `tirith __capsule-child` launcher, NOT inside
# `pre_exec`. `cfg(target_os = "linux")`-gated so macOS / Windows targets compile
# without them. Proven to resolve + build on MSRV 1.83 across the linux-gnu target
# in the PR-0 spike. Licenses: landlock MIT/Apache-2.0, extrasafe MIT.
# without them. Proven to resolve + build on Rust 1.83 across the linux-gnu target
# in the PR-0 spike, below the current 1.88 workspace floor. Licenses: landlock
# MIT/Apache-2.0, extrasafe MIT.
[target.'cfg(target_os = "linux")'.dependencies]
landlock = "0.4"
extrasafe = "0.5"
Expand Down
11 changes: 11 additions & 0 deletions crates/tirith-core/assets/data/rule_explanations.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,17 @@ examples_good = ["A normal log file that ends with a newline or any non-escape b
false_positive_guidance = "True-positive in practice: well-formed terminal output terminates its OSC/CSI sequences and keeps control payloads bounded. A truncated or oversized sequence usually means the producer was cut off, hostile, or tampered with."
remediation = "Under fail-closed mode the wrapping response is replaced with a placeholder. Under fail-open the finding is surfaced as a warning; consult the audit log and re-fetch the source if possible."

[[rule]]
id = "output_analysis_overflow"
title = "Output evidence exceeded bounded analyzer retention"
category = "output"
severity_rationale = "High — the byte scanner dropped escape-sequence evidence past its per-class retention cap, so the stream was analyzed with partial evidence. Treating this as analysis-incomplete prevents an attacker from flooding harmless sequences to push real evidence past the cap."
description = "The streamed output contained more escape-sequence hits than the analyzer retains per evidence class. Excess hits were counted and dropped instead of allocated, so memory stays bounded; the finding marks the resulting analysis as partial."
examples_bad = ["A 16 MiB tool response consisting of millions of four-byte SGR resets — designed to amplify analyzer memory or push a later malicious sequence past the evidence cap."]
examples_good = ["Ordinary terminal output with far fewer than the per-class hit cap of escape sequences."]
false_positive_guidance = "Effectively never: legitimate terminal streams stay orders of magnitude below the cap. Reaching it indicates an adversarial or pathological producer."
remediation = "Fail-closed callers should deny the stream. Re-fetch or re-run the producer if the content is legitimately needed; the cap itself is deliberate and not tunable per stream."

# M7 ch5 — prompt-injection seed phrases.

[[rule]]
Expand Down
1 change: 1 addition & 0 deletions crates/tirith-core/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,7 @@ const EXPECTED_RULES: &[(&str, &str)] = &[
"output_truncated_escape_sequence",
"OutputTruncatedEscapeSequence",
),
("output_analysis_overflow", "OutputAnalysisOverflow"),
// M7 ch5 — prompt-injection seed phrases.
("prompt_injection_in_output", "PromptInjectionInOutput"),
("ignore_previous_instructions", "IgnorePreviousInstructions"),
Expand Down
Loading
Loading