commit aa1c9b5a62ea782f3ac8470f433acfa30920aa94
parent 8f5156a06cf6c84ea3c85c95e096bbfc6800be61
Author: triesap <tyson@radroots.org>
Date: Mon, 10 Aug 2026 17:33:27 +0000
policy: require revision-pinned Git dependencies
- require immutable revision selectors for every accepted Cargo Git source
- validate manifest pins and lockfile resolutions against the source allowlist
- reject branch, tag, unpinned, and unknown dependency fixtures
- retain the exact direct rust-nostr revision without broadening policy exceptions
Diffstat:
3 files changed, 115 insertions(+), 0 deletions(-)
diff --git a/build.gradle.kts b/build.gradle.kts
@@ -1,4 +1,5 @@
import org.harvestcircle.gradle.VerifyFoundationBoundaries
+import org.harvestcircle.gradle.VerifyGitSourcePolicy
import org.harvestcircle.gradle.VerifyProductCoordinateConsumers
import org.harvestcircle.gradle.VerifyProductCoordinates
import org.harvestcircle.gradle.VerifyVerificationLanes
@@ -73,6 +74,17 @@ val verifyFoundationArchive by tasks.registering(VerifyFoundationBoundaries::cla
gitAware.set(false)
}
+val verifyGitSourcePolicy by tasks.registering(VerifyGitSourcePolicy::class) {
+ group = "verification"
+ description = "Validates immutable and allowlisted Cargo Git dependency sources."
+ denyConfigFile.set(layout.projectDirectory.file("core/deny.toml"))
+ cargoLockFile.set(layout.projectDirectory.file("core/Cargo.lock"))
+ cargoManifestFiles.from(fileTree("core") { include("Cargo.toml", "crates/*/Cargo.toml") })
+}
+
+verifyFoundationBoundaries.configure { dependsOn(verifyGitSourcePolicy) }
+verifyFoundationArchive.configure { dependsOn(verifyGitSourcePolicy) }
+
providers.environmentVariable("EXT_BUILD_GRADLE_BUILD_DIR").orNull?.let { extBuildGradleRoot ->
layout.buildDirectory.set(file(extBuildGradleRoot).resolve("root"))
}
diff --git a/buildSrc/src/main/kotlin/org/harvestcircle/gradle/GitSourcePolicy.kt b/buildSrc/src/main/kotlin/org/harvestcircle/gradle/GitSourcePolicy.kt
@@ -0,0 +1,102 @@
+package org.harvestcircle.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+
+object GitSourcePolicy {
+ private val gitExpression = Regex("""git\s*=\s*"([^"]+)"""")
+ private val revisionExpression = Regex("""rev\s*=\s*"([0-9a-f]{40})"""")
+ private val forbiddenSpec = Regex("""(?:branch|tag)\s*=""")
+
+ fun validateDependency(
+ expression: String,
+ allowedGit: Set<String>,
+ ): String? {
+ val git = gitExpression.find(expression)?.groupValues?.get(1) ?: return null
+ require(git in allowedGit) { "Git dependency source is not allowlisted: $git" }
+ require(!forbiddenSpec.containsMatchIn(expression)) { "Git dependency uses a branch or tag" }
+ val revisions = revisionExpression.findAll(expression).map { it.groupValues[1] }.toList()
+ require(revisions.size == 1) { "Git dependency must use exactly one full revision pin" }
+ return revisions.single()
+ }
+}
+
+abstract class VerifyGitSourcePolicy : DefaultTask() {
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val denyConfigFile: RegularFileProperty
+
+ @get:InputFile
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val cargoLockFile: RegularFileProperty
+
+ @get:InputFiles
+ @get:PathSensitive(PathSensitivity.RELATIVE)
+ abstract val cargoManifestFiles: ConfigurableFileCollection
+
+ @TaskAction
+ fun verify() {
+ val denyConfig = denyConfigFile.get().asFile.readText()
+ check(Regex("(?m)^required-git-spec\\s*=\\s*\"rev\"$").containsMatchIn(denyConfig)) {
+ "cargo-deny must require revision-pinned Git sources"
+ }
+ val allowedGit =
+ Regex("(?s)allow-git\\s*=\\s*\\[(.*?)]")
+ .find(denyConfig)
+ ?.groupValues
+ ?.get(1)
+ ?.let { block -> Regex("\"([^\"]+)\"").findAll(block).map { it.groupValues[1] }.toSet() }
+ .orEmpty()
+ check(allowedGit.isNotEmpty()) { "cargo-deny Git allowlist is empty" }
+
+ val revisions = mutableMapOf<String, MutableSet<String>>()
+ cargoManifestFiles.files.sortedBy { it.path }.forEach { manifest ->
+ manifest.readLines().forEachIndexed { index, line ->
+ if (!line.contains("git")) return@forEachIndexed
+ val git = Regex("""git\s*=\s*"([^"]+)""").find(line)?.groupValues?.get(1) ?: return@forEachIndexed
+ val revision =
+ runCatching { GitSourcePolicy.validateDependency(line, allowedGit) }
+ .getOrElse { error("${manifest.path}:${index + 1}: ${it.message}") }
+ ?: return@forEachIndexed
+ revisions.getOrPut(git) { mutableSetOf() } += revision
+ }
+ }
+ check(revisions.isNotEmpty()) { "No revision-pinned Git dependencies were inspected" }
+ check(
+ revisions["https://github.com/rust-nostr/nostr.git"] ==
+ setOf("5bba5163eb77107f82c4a8262cf29d7f33a73219"),
+ ) { "The direct rust-nostr revision changed" }
+
+ cargoLockFile.get().asFile.useLines { lines ->
+ lines.filter { it.startsWith("source = \"git+") }.forEach { source ->
+ check(Regex("\\?rev=[0-9a-f]{40}#[0-9a-f]{40}\"$").containsMatchIn(source)) {
+ "Cargo.lock contains a Git source without an immutable revision: $source"
+ }
+ }
+ }
+
+ val allowed = allowedGit.first()
+ check(
+ GitSourcePolicy.validateDependency(
+ "dependency = { git = \"$allowed\", rev = \"${"a".repeat(40)}\" }",
+ allowedGit,
+ ) == "a".repeat(40),
+ )
+ listOf(
+ "dependency = { git = \"$allowed\", branch = \"main\" }",
+ "dependency = { git = \"$allowed\", tag = \"v1.0.0\" }",
+ "dependency = { git = \"$allowed\" }",
+ "dependency = { git = \"https://example.invalid/repository\", rev = \"${"b".repeat(40)}\" }",
+ ).forEach { fixture ->
+ check(runCatching { GitSourcePolicy.validateDependency(fixture, allowedGit) }.isFailure) {
+ "Git source policy accepted a mutable or unknown fixture"
+ }
+ }
+ }
+}
diff --git a/core/deny.toml b/core/deny.toml
@@ -25,6 +25,7 @@ confidence-threshold = 0.93
[sources]
unknown-registry = "deny"
unknown-git = "deny"
+required-git-spec = "rev"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [
"https://github.com/radrootslabs/lib",