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
8 changes: 6 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Outbound connections are limited to:

| Destination | Purpose | Auth | Failure mode |
|-------------|---------|------|-------------|
| `api.github.com` | Check for releases and, for explicitly configured review sources, verify and perform repository reads | Public requests by default; optional read-only credential from the OS keyring | Public checks are best effort; configured private access fails closed when credentials or permissions cannot be verified |
| `api.github.com` | Check for releases, discover the pull request for a checkout whose `origin` is on GitHub, and read its reviews and checks | The local GitHub login (`GH_TOKEN`, `gh auth token`, or the git credential helper) when present, anonymous otherwise; optional read-only credential from the OS keyring for configured private sources | Public checks are best effort; an anonymous read GitHub refuses is reported as `denied_no_credential`; configured private access fails closed when credentials or permissions cannot be verified |
| `github.com` | Download binary during `tracedecay upgrade` | None (public releases) | Error shown to user |
| `huggingface.co` and Hugging Face artifact hosts | Download missing, revision-pinned semantic-model artifacts when semantic auto-download is enabled | None | Semantic retrieval reports model acquisition state or failure; exact, lexical, and graph retrieval remain available |
| `tracedecay-counter.enzinol.workers.dev` | Aggregate token-savings counter | None | Silently ignored |
Expand All @@ -78,7 +78,11 @@ SHA-256 digests before publication, and can be disabled with `HF_HUB_OFFLINE`.
### Credentials and secrets

TraceDecay does not require credentials for its default local and public
repository behavior. A user may explicitly configure a private GitHub review
repository behavior. When `GH_TOKEN`, a `gh` login, or a git credential helper
login for `https://github.com` exists, GitHub reads for the checkout's `origin`
use it; the token is read into zeroizing memory per use and never stored. The
git credential helper is asked only for `protocol=https`/`host=github.com`, with
terminal prompts disabled. A user may explicitly configure a private GitHub review
source with `access = "os_keyring"` and keyring service/account locators. The
secret remains in the operating-system keyring; configuration stores only its
locator. The daemon reads it into zeroizing memory, sends it only to GitHub
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
{
"capture": {
"captured_at": "2026-09-26T12:20:00Z",
"method": "GET",
"url": "https://api.github.com/repos/rust-lang/log/pulls/741/comments?per_page=100",
"authentication": "none",
"documentation": "https://docs.github.com/en/rest/pulls/comments#list-review-comments-on-a-pull-request"
},
"response": [
{
"url": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069686687",
"pull_request_review_id": 5275645212,
"id": 4069686687,
"node_id": "PRRC_kwDOAarcas7ykn2f",
"diff_hunk": "@@ -123,12 +120,24 @@ jobs:\n with:\n components: clippy\n toolchain: \"1.71\"\n+ - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml\n+ - run: cargo run --verbose --manifest-path test_max_level_features/Cargo.toml --release\n+\n+ msrv-test:\n+ name: MSRV test\n+ runs-on: ubuntu-latest\n+ steps:\n+ - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2\n+ with:\n+ persist-credentials: false\n+ - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1\n+ with:\n+ components: clippy",
"path": ".github/workflows/main.yml",
"commit_id": "1a4b67cfc41237e673dafd0dfc414577f0b5d327",
"original_commit_id": "1a4b67cfc41237e673dafd0dfc414577f0b5d327",
"user": {
"login": "Thomasdezeeuw",
"id": 3159064,
"node_id": "MDQ6VXNlcjMxNTkwNjQ=",
"avatar_url": "https://avatars.githubusercontent.com/u/3159064?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Thomasdezeeuw",
"html_url": "https://github.com/Thomasdezeeuw",
"followers_url": "https://api.github.com/users/Thomasdezeeuw/followers",
"following_url": "https://api.github.com/users/Thomasdezeeuw/following{/other_user}",
"gists_url": "https://api.github.com/users/Thomasdezeeuw/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Thomasdezeeuw/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Thomasdezeeuw/subscriptions",
"organizations_url": "https://api.github.com/users/Thomasdezeeuw/orgs",
"repos_url": "https://api.github.com/users/Thomasdezeeuw/repos",
"events_url": "https://api.github.com/users/Thomasdezeeuw/events{/privacy}",
"received_events_url": "https://api.github.com/users/Thomasdezeeuw/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"body": "I don't think we need Clippy here? Same for the msrv-check job.\n\n*[View changes since the review](https://triagebot.infra.rust-lang.org/gh-changes-since/rust-lang/log/741/8034743dd9d7f7583bd9a670271483d176130911..1a4b67cfc41237e673dafd0dfc414577f0b5d327)*",
"created_at": "2026-09-22T08:19:31Z",
"updated_at": "2026-09-22T08:26:05Z",
"html_url": "https://github.com/rust-lang/log/pull/741#discussion_r4069686687",
"pull_request_url": "https://api.github.com/repos/rust-lang/log/pulls/741",
"_links": {
"self": {
"href": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069686687"
},
"html": {
"href": "https://github.com/rust-lang/log/pull/741#discussion_r4069686687"
},
"pull_request": {
"href": "https://api.github.com/repos/rust-lang/log/pulls/741"
}
},
"reactions": {
"url": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069686687/reactions",
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"hooray": 0,
"confused": 0,
"heart": 0,
"rocket": 0,
"eyes": 0
},
"start_line": null,
"original_start_line": null,
"start_side": null,
"line": 135,
"original_line": 135,
"side": "RIGHT",
"author_association": "COLLABORATOR",
"original_position": 39,
"position": 39,
"subject_type": "line"
},
{
"url": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069691901",
"pull_request_review_id": 5275645212,
"id": 4069691901,
"node_id": "PRRC_kwDOAarcas7ykpH9",
"diff_hunk": "@@ -111,9 +108,9 @@ jobs:\n - run: cargo build --verbose -Z minimal-versions --features \"kv kv_serde\"\n - run: cargo build --verbose -Z minimal-versions --features \"kv kv_std\"\n - run: cargo build --verbose -Z minimal-versions --features \"kv kv_sval kv_serde\"\n-\n- msrv:\n- name: MSRV\n+ \n+ msrv-check:\n+ name: MSRV build",
"path": ".github/workflows/main.yml",
"commit_id": "1a4b67cfc41237e673dafd0dfc414577f0b5d327",
"original_commit_id": "1a4b67cfc41237e673dafd0dfc414577f0b5d327",
"user": {
"login": "Thomasdezeeuw",
"id": 3159064,
"node_id": "MDQ6VXNlcjMxNTkwNjQ=",
"avatar_url": "https://avatars.githubusercontent.com/u/3159064?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/Thomasdezeeuw",
"html_url": "https://github.com/Thomasdezeeuw",
"followers_url": "https://api.github.com/users/Thomasdezeeuw/followers",
"following_url": "https://api.github.com/users/Thomasdezeeuw/following{/other_user}",
"gists_url": "https://api.github.com/users/Thomasdezeeuw/gists{/gist_id}",
"starred_url": "https://api.github.com/users/Thomasdezeeuw/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/Thomasdezeeuw/subscriptions",
"organizations_url": "https://api.github.com/users/Thomasdezeeuw/orgs",
"repos_url": "https://api.github.com/users/Thomasdezeeuw/repos",
"events_url": "https://api.github.com/users/Thomasdezeeuw/events{/privacy}",
"received_events_url": "https://api.github.com/users/Thomasdezeeuw/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"body": "Nit: the job short name is check, the display name is build and we're actually running of the test, probably want to make that consistent.\n\n*[View changes since the review](https://triagebot.infra.rust-lang.org/gh-changes-since/rust-lang/log/741/8034743dd9d7f7583bd9a670271483d176130911..1a4b67cfc41237e673dafd0dfc414577f0b5d327)*",
"created_at": "2026-09-22T08:20:15Z",
"updated_at": "2026-09-22T08:26:06Z",
"html_url": "https://github.com/rust-lang/log/pull/741#discussion_r4069691901",
"pull_request_url": "https://api.github.com/repos/rust-lang/log/pulls/741",
"_links": {
"self": {
"href": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069691901"
},
"html": {
"href": "https://github.com/rust-lang/log/pull/741#discussion_r4069691901"
},
"pull_request": {
"href": "https://api.github.com/repos/rust-lang/log/pulls/741"
}
},
"reactions": {
"url": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069691901/reactions",
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"hooray": 0,
"confused": 0,
"heart": 0,
"rocket": 0,
"eyes": 0
},
"start_line": null,
"original_start_line": null,
"start_side": null,
"line": 113,
"original_line": 113,
"side": "RIGHT",
"author_association": "COLLABORATOR",
"original_position": 19,
"position": 19,
"subject_type": "line"
},
{
"url": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069777906",
"pull_request_review_id": 5275761549,
"id": 4069777906,
"node_id": "PRRC_kwDOAarcas7yk-Hy",
"diff_hunk": "@@ -111,9 +108,9 @@ jobs:\n - run: cargo build --verbose -Z minimal-versions --features \"kv kv_serde\"\n - run: cargo build --verbose -Z minimal-versions --features \"kv kv_std\"\n - run: cargo build --verbose -Z minimal-versions --features \"kv kv_sval kv_serde\"\n-\n- msrv:\n- name: MSRV\n+ \n+ msrv-check:\n+ name: MSRV build",
"path": ".github/workflows/main.yml",
"commit_id": "1a4b67cfc41237e673dafd0dfc414577f0b5d327",
"original_commit_id": "1a4b67cfc41237e673dafd0dfc414577f0b5d327",
"user": {
"login": "KodrAus",
"id": 6721458,
"node_id": "MDQ6VXNlcjY3MjE0NTg=",
"avatar_url": "https://avatars.githubusercontent.com/u/6721458?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/KodrAus",
"html_url": "https://github.com/KodrAus",
"followers_url": "https://api.github.com/users/KodrAus/followers",
"following_url": "https://api.github.com/users/KodrAus/following{/other_user}",
"gists_url": "https://api.github.com/users/KodrAus/gists{/gist_id}",
"starred_url": "https://api.github.com/users/KodrAus/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/KodrAus/subscriptions",
"organizations_url": "https://api.github.com/users/KodrAus/orgs",
"repos_url": "https://api.github.com/users/KodrAus/repos",
"events_url": "https://api.github.com/users/KodrAus/events{/privacy}",
"received_events_url": "https://api.github.com/users/KodrAus/received_events",
"type": "User",
"user_view_type": "public",
"site_admin": false
},
"body": "Fair 👍 I’ll clean these up",
"created_at": "2026-09-22T08:32:38Z",
"updated_at": "2026-09-22T08:32:39Z",
"html_url": "https://github.com/rust-lang/log/pull/741#discussion_r4069777906",
"pull_request_url": "https://api.github.com/repos/rust-lang/log/pulls/741",
"_links": {
"self": {
"href": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069777906"
},
"html": {
"href": "https://github.com/rust-lang/log/pull/741#discussion_r4069777906"
},
"pull_request": {
"href": "https://api.github.com/repos/rust-lang/log/pulls/741"
}
},
"reactions": {
"url": "https://api.github.com/repos/rust-lang/log/pulls/comments/4069777906/reactions",
"total_count": 0,
"+1": 0,
"-1": 0,
"laugh": 0,
"hooray": 0,
"confused": 0,
"heart": 0,
"rocket": 0,
"eyes": 0
},
"start_line": null,
"original_start_line": null,
"start_side": null,
"line": 113,
"original_line": 113,
"side": "RIGHT",
"in_reply_to_id": 4069691901,
"author_association": "CONTRIBUTOR",
"original_position": 19,
"position": 19,
"subject_type": "line"
}
]
}
8 changes: 6 additions & 2 deletions crates/tracedecay-application/src/advisory/github_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ use tracedecay_domain::{ManifestDigest, canonical_sha256};

use super::{GitHubReadOnlyTransport, GitHubRestDescriptorV1, context_allows_feedback_operation};

pub use access::ConfiguredGitHubSourceAccessAuthorityV1;
pub use access::{
ConfiguredGitHubSourceAccessAuthorityV1, DAEMON_GITHUB_ORIGIN_SOURCE_BINDING_ID,
daemon_owned_github_source_binding_v1, github_repository_from_remote_v1,
};
pub use anchors::{
GitHubReviewBodyEvidenceAuthorityV1, GitHubReviewBodyEvidenceV1, GitHubReviewBodyReadOutcomeV1,
ProjectGitHubAnchorAuthorityV1, ProjectGitHubRegistrarAuthoritiesV1,
Expand All @@ -60,7 +63,8 @@ pub use decoder::{
};
pub use discovery::{
GitHubDiscoveryControlV1, GitHubExactCommitDiscoveryOutcomeV1, GitHubExactCommitPullRequestV1,
discover_exact_commit_pull_request_v1,
GitHubPullRequestDiscoveryKindV1, GitHubSourceStateV1, GitHubSourceStatusV1,
discover_exact_commit_pull_request_v1, github_source_status_v1, record_github_source_status_v1,
};
pub use dto::{
GitHubActionsCheckRunOutputV1, GitHubActionsCheckRunV1, GitHubActionsCheckSuiteRefV1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ use tracedecay_contracts::feedback::{
GITHUB_REVIEW_INGEST_CAPABILITY_ID_V1, GitHubReviewReadRequestV1, feedback_surface_operation,
};
use tracedecay_contracts::{AuthorizationRequest, ResolvedScope, now_micros};
use tracedecay_domain::configuration::SourceKindV1;
use tracedecay_domain::{LocatorDigest, canonical_sha256};
use tracedecay_domain::configuration::{
AuthorityRef, ScopeSourceBinding, SourceBindingId, SourceKindV1,
};
use tracedecay_domain::feedback::GitHubPullRequestIdV1;
use tracedecay_domain::{LocatorDigest, ProjectId, canonical_sha256};

use super::{GitHubProviderLifecycleV1, GitHubSourceAccessAuthorityV1};
use super::{GitHubProviderLifecycleV1, GitHubRepositoryTargetV1, GitHubSourceAccessAuthorityV1};
use crate::advisory::ci_runtime::{CiSourceAccessAuthorityV1, CiSourceAccessOutcomeV1};
use crate::source_authorization::{
ProjectSourceAccessOutcome, project_source_access_snapshot_for_request,
Expand Down Expand Up @@ -151,6 +154,10 @@ where
}
}

/// Identifier of the one daemon-owned GitHub binding derived from a
/// checkout's `origin` remote.
pub const DAEMON_GITHUB_ORIGIN_SOURCE_BINDING_ID: &str = "binding.tracedecay-daemon.github-origin";

fn github_source_locator(repository_owner: &str, repository_name: &str) -> Option<LocatorDigest> {
if repository_owner.is_empty() || repository_name.is_empty() {
return None;
Expand All @@ -163,3 +170,63 @@ fn github_source_locator(repository_owner: &str, repository_name: &str) -> Optio
.ok()?;
LocatorDigest::new(digest.as_str()).ok()
}

/// The daemon-owned GitHub source binding for `owner/repository`, the
/// repository GitHub reads for this project are authorized against.
pub fn daemon_owned_github_source_binding_v1(
project_id: &ProjectId,
repository_owner: &str,
repository_name: &str,
) -> Option<ScopeSourceBinding> {
ScopeSourceBinding::new(
SourceBindingId::new(DAEMON_GITHUB_ORIGIN_SOURCE_BINDING_ID).ok()?,
SourceKindV1::GitHub,
github_source_locator(repository_owner, repository_name)?,
AuthorityRef::Project(project_id.clone()),
)
.ok()
}

/// `(owner, repository)` of a `github.com` remote URL, or `None` for any
/// other host, credential-bearing URL, or path shape.
pub fn github_repository_from_remote_v1(remote: &str) -> Option<(String, String)> {
let (owner, repository) = if let Ok(url) = url::Url::parse(remote) {
if (url.scheme() != "https" && url.scheme() != "ssh")
|| !url.host_str()?.eq_ignore_ascii_case("github.com")
|| url.password().is_some()
|| (url.scheme() == "https" && !url.username().is_empty())
|| (url.scheme() == "ssh" && url.username() != "git")
|| url.query().is_some()
|| url.fragment().is_some()
{
return None;
}
let segments = url.path_segments()?.collect::<Vec<_>>();
if segments.len() != 2 {
return None;
}
(segments[0].to_owned(), segments[1].to_owned())
} else {
let remote = remote.strip_prefix("git@github.com:")?;
let mut segments = remote.split('/');
let owner = segments.next()?;
let repository = segments.next()?;
if segments.next().is_some() {
return None;
}
(owner.to_owned(), repository.to_owned())
};
let repository = repository
.strip_suffix(".git")
.unwrap_or(&repository)
.to_owned();
let target = GitHubRepositoryTargetV1 {
owner,
repository,
pull_request_number: 1,
pull_request_id: GitHubPullRequestIdV1::new("1").ok()?,
};
target
.validate()
.then_some((target.owner, target.repository))
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl ProjectGitHubAnchorAuthorityV1 {
seed: &GitHubReviewAnchorSeedV1,
stored: StoredGitHubAnchorV1,
) -> Option<GitHubCanonicalReviewAnchorsV1> {
if !same_original_locator(&stored.seed, seed) {
if !same_code_location(&stored.seed, seed) {
return None;
}
let original = stored.anchors.original;
Expand Down Expand Up @@ -936,12 +936,11 @@ fn body_sanitization_receipt(
.ok()
}

fn same_original_locator(
left: &GitHubReviewAnchorSeedV1,
right: &GitHubReviewAnchorSeedV1,
) -> bool {
left.comment_id == right.comment_id
&& left.path == right.path
/// The code anchor is keyed by location alone, so every comment on the same
/// original lines (a reply thread) shares it; the per-comment author, body,
/// and URL anchors are derived from each comment's own seed.
fn same_code_location(left: &GitHubReviewAnchorSeedV1, right: &GitHubReviewAnchorSeedV1) -> bool {
left.path == right.path
&& left.original_commit_id == right.original_commit_id
&& left.original_start_line == right.original_start_line
&& left.original_line == right.original_line
Expand Down
Loading
Loading