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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ cargo run -- release-audit
Run the strict pre-push audit with a configured signing key file:

```sh
AGENTK_REQUIRE_SIGNING_KEY=1 AGENTK_SIGNING_KEY_FILE=../agentk-signing-key cargo run -- release-audit --strict
AGENTK_REQUIRE_SIGNING_KEY=1 \
AGENTK_RELEASE_REMOTE_APPROVED=1 \
AGENTK_SIGNING_KEY_FILE=../agentk-signing-key \
cargo run -- release-audit --strict
```

Contribution and release rules live in [CONTRIBUTING.md](CONTRIBUTING.md),
Expand Down Expand Up @@ -452,7 +455,7 @@ Not implemented yet:
- real sandboxing,
- eBPF/cgroup enforcement.

By default AgentK signs evidence with a static development key. Set `AGENTK_SIGNING_KEY_FILE` to a private key file created by `agentk keygen`, or set `AGENTK_SIGNING_KEY_HEX` to a 32-byte hex Ed25519 signing key for non-demo runs. Set `AGENTK_REQUIRE_SIGNING_KEY=1` in release gates to fail readiness if the configured signer falls back to the development key. On Unix, readiness also fails if the configured key file is readable by group/other users or if its parent directory is group/other writable. The CLI only prints the public key.
By default AgentK signs evidence with a static development key. Set `AGENTK_SIGNING_KEY_FILE` to a private key file created by `agentk keygen`, or set `AGENTK_SIGNING_KEY_HEX` to a 32-byte hex Ed25519 signing key for non-demo runs. Set `AGENTK_REQUIRE_SIGNING_KEY=1` in release gates to fail readiness if the configured signer falls back to the development key. Set `AGENTK_RELEASE_REMOTE_APPROVED=1` only after release approval and branch-protection review so strict release gates can pass with the approved public remote configured. On Unix, readiness also fails if the configured key file is readable by group/other users or if its parent directory is group/other writable. The CLI only prints the public key.

See [SECURITY.md](SECURITY.md), [docs/threat-model.md](docs/threat-model.md), [docs/key-lifecycle.md](docs/key-lifecycle.md), [docs/mcp-proxy.md](docs/mcp-proxy.md), and [docs/public-readiness.md](docs/public-readiness.md).

Expand Down
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ AGENTK_SIGNING_KEY_FILE set -> file signing key
unset -> static development key
invalid -> readiness failure
AGENTK_REQUIRE_SIGNING_KEY=1 -> readiness failure unless AGENTK_SIGNING_KEY_HEX or AGENTK_SIGNING_KEY_FILE is valid
AGENTK_RELEASE_REMOTE_APPROVED=1 -> strict release gate accepts approved git remote
```

On Unix, readiness also verifies that an `AGENTK_SIGNING_KEY_FILE` path is owner-only and that its parent directory is not group/other writable, so loose custody permissions block release gates without printing the local path.
Expand Down
5 changes: 4 additions & 1 deletion docs/key-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ release evidence.
Release gates must require a configured signer:

```sh
AGENTK_REQUIRE_SIGNING_KEY=1 AGENTK_SIGNING_KEY_FILE=../agentk-release-signing-key cargo run --locked -- release-audit --strict
AGENTK_REQUIRE_SIGNING_KEY=1 \
AGENTK_RELEASE_REMOTE_APPROVED=1 \
AGENTK_SIGNING_KEY_FILE=../agentk-release-signing-key \
cargo run --locked -- release-audit --strict
```

The static development signer is acceptable only for demos and CI smoke checks.
Expand Down
9 changes: 7 additions & 2 deletions docs/public-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ keep the same checks in CI and protect the default branch.

## Pre-Public Repository Hygiene

- [ ] No git remote configured.
- [ ] No git remote configured before first public push, or
`AGENTK_RELEASE_REMOTE_APPROVED=1` is set only after explicit release
approval and branch-protection review.
- [ ] No generated `.agentk/` logs tracked.
- [ ] No local paths, usernames, real URLs, or private traces in docs/tests.
- [ ] No API keys, tokens, certs, private keys, or `.env` files.
Expand Down Expand Up @@ -82,7 +84,10 @@ Before first public push:
```txt
git remote -v
git status --short
AGENTK_REQUIRE_SIGNING_KEY=1 AGENTK_SIGNING_KEY_FILE=../agentk-signing-key cargo run -- release-audit --strict
AGENTK_REQUIRE_SIGNING_KEY=1 \
AGENTK_RELEASE_REMOTE_APPROVED=1 \
AGENTK_SIGNING_KEY_FILE=../agentk-signing-key \
cargo run -- release-audit --strict
cargo fmt --check
cargo test
cargo clippy --all-targets --all-features
Expand Down
5 changes: 4 additions & 1 deletion docs/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ cargo run -- key-rotate-verify --manifest docs/key-rotation-vNEXT.json
Run the strict audit with the local release key:

```sh
AGENTK_REQUIRE_SIGNING_KEY=1 AGENTK_SIGNING_KEY_FILE=../agentk-release-signing-key cargo run --locked -- release-audit --strict
AGENTK_REQUIRE_SIGNING_KEY=1 \
AGENTK_RELEASE_REMOTE_APPROVED=1 \
AGENTK_SIGNING_KEY_FILE=../agentk-release-signing-key \
cargo run --locked -- release-audit --strict
```

On Unix, the audit fails if the release key file is group- or world-readable or
Expand Down
1 change: 1 addition & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ Status: in progress.
- [x] Protect the default branch with the CI `audit` check.
- [x] Add contributor guidelines for security-sensitive changes.
- [x] Add a signed release checklist for tagged versions.
- [x] Add an explicit remote-approval signal for strict release gates.

## Milestone 6: v0.1 Release Shape

Expand Down
1 change: 1 addition & 0 deletions docs/v0.1-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ a durable signing key outside the repository:

```sh
AGENTK_REQUIRE_SIGNING_KEY=1 \
AGENTK_RELEASE_REMOTE_APPROVED=1 \
AGENTK_SIGNING_KEY_FILE=<path-outside-repo> \
cargo run --locked -- release-audit --strict
```
Expand Down
56 changes: 47 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const DEV_SIGNING_KEY_BYTES: [u8; 32] = [0x41; 32];
pub const SIGNING_KEY_ENV: &str = "AGENTK_SIGNING_KEY_HEX";
pub const SIGNING_KEY_FILE_ENV: &str = "AGENTK_SIGNING_KEY_FILE";
pub const REQUIRE_SIGNING_KEY_ENV: &str = "AGENTK_REQUIRE_SIGNING_KEY";
pub const RELEASE_REMOTE_APPROVED_ENV: &str = "AGENTK_RELEASE_REMOTE_APPROVED";

#[derive(Debug, Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -11463,15 +11464,7 @@ fn check_git_remote(root: &Path) -> ReadinessCheck {
{
Ok(output) if output.status.success() => {
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.trim().is_empty() {
readiness_check("git remote", ReadinessStatus::Pass, "no remotes configured")
} else {
readiness_check(
"git remote",
ReadinessStatus::Warn,
"remote configured; verify release approval and branch protection",
)
}
check_git_remote_output(&stdout, release_remote_approved())
}
Ok(output) => readiness_check(
"git remote",
Expand All @@ -11486,6 +11479,32 @@ fn check_git_remote(root: &Path) -> ReadinessCheck {
}
}

fn release_remote_approved() -> bool {
env_flag_enabled(env::var(RELEASE_REMOTE_APPROVED_ENV).ok().as_deref())
}

fn check_git_remote_output(stdout: &str, release_remote_approved: bool) -> ReadinessCheck {
if stdout.trim().is_empty() {
readiness_check("git remote", ReadinessStatus::Pass, "no remotes configured")
} else if release_remote_approved {
readiness_check(
"git remote",
ReadinessStatus::Pass,
format!(
"remote configured with explicit release approval via {RELEASE_REMOTE_APPROVED_ENV}; verify branch protection"
),
)
} else {
readiness_check(
"git remote",
ReadinessStatus::Warn,
format!(
"remote configured; set {RELEASE_REMOTE_APPROVED_ENV}=1 only after release approval and branch protection review"
),
)
}
}

fn check_gitignore(root: &Path) -> ReadinessCheck {
match fs::read_to_string(root.join(".gitignore")) {
Ok(content) if content.lines().any(|line| line.trim() == ".agentk/") => readiness_check(
Expand Down Expand Up @@ -17180,6 +17199,25 @@ done
}
}

#[test]
fn git_remote_warning_requires_explicit_release_approval() {
let no_remote = check_git_remote_output("", false);
assert_eq!(no_remote.status, ReadinessStatus::Pass);

let configured_remote = "origin\thttps://github.com/Atomics-hub/agentk.git (fetch)\n";
let without_approval = check_git_remote_output(configured_remote, false);
assert_eq!(without_approval.status, ReadinessStatus::Warn);
assert!(
without_approval
.detail
.contains(RELEASE_REMOTE_APPROVED_ENV)
);

let with_approval = check_git_remote_output(configured_remote, true);
assert_eq!(with_approval.status, ReadinessStatus::Pass);
assert!(with_approval.detail.contains(RELEASE_REMOTE_APPROVED_ENV));
}

#[test]
fn release_audit_passes_with_warnings_but_not_failures() {
let warn_only = release_audit_from_checks(
Expand Down