Skip to content

Commit 59c0dfa

Browse files
committed
Consolidate source of truth after Phase 15 review
1 parent c1d5512 commit 59c0dfa

4 files changed

Lines changed: 128 additions & 24 deletions

File tree

PROJEKT.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ CompText CLI is an experimental terminal context client for building determinist
2020
### Current State
2121
```text
2222
CURRENT_PHASE: 15
23-
CURRENT_TASK: Cryptographic Provenance Engine
24-
LAST_GREEN_PHASE: 15
25-
STATUS: complete
23+
CURRENT_TASK: Cryptographic Provenance Engine Review-Gate cleanup
24+
LAST_GREEN_PHASE: 14
25+
STATUS: blocked
2626
```
2727

2828
### Autonomy Contract
2929
- **Allowed Modifications**: May edit source code (`src/**`), tests (`tests/**`), docs (`docs/**`), skills (`.agent/skills/**`, `.agents/skills/**`), prompts (`prompts/**`), and configurations (`Cargo.toml`, `comptext.example.toml`).
3030
- **Allowed Commands**: May run local compilation, lint checks, tests, and formatting validation.
3131
- **Error Remediation**: May automatically modify code to fix local build, format, test, or clippy failures.
32-
- **Phase Transition**: May commit and push changes after all validation passes for a green phase, and automatically proceed to the next phase queue item.
32+
- **Phase Transition**: May commit and push changes after all validation passes for a green phase, and await Review-Gate feedback before transitioning to any new phase.
3333

3434
### Forbidden Rules
3535
- **No Private Keys / Secrets**: Forbidden to read or parse `.env`, `.env.*`, `.netrc`, `.git-credentials`, private keys (`*.key`, `*.pem`), or credentials.
@@ -89,7 +89,7 @@ git push
8989
| **Phase 12** | Antigravity CLI Governance & Token Economy | Antigravity governance docs, token economy rules, skill/hook/permission target architecture | **COMPLETE** |
9090
| **Phase 13** | Skill Bundle Registry | Local skill bundle registry and starter skill templates | **COMPLETE** |
9191
| **Phase 14** | Hook/Permission Integration | Hook boundaries, dynamic run approvals | **COMPLETE** |
92-
| **Phase 15** | Cryptographic Provenance Engine | Signed evidence trail generation and cryptographic integrity seals | **COMPLETE** |
92+
| **Phase 15** | Cryptographic Provenance Engine | local SHA-256 provenance manifests | **BLOCKED** |
9393

9494
---
9595

docs/PROVENANCE_MODEL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This document outlines the design and local verification guidelines for the Comp
99
CompText utilizes local provenance manifests to track artifact changes and link them back to their origin task context.
1010

1111
- **Schema Definition**: Provenance manifests are stored as JSON files with the `.provenance.json` extension alongside their matching artifact.
12-
- **Canonical Hash**: Checksums are computed entirely offline using a self-contained SHA-256 algorithm.
12+
- **Raw file content SHA-256**: Checksums are computed entirely offline using a self-contained SHA-256 algorithm.
1313
- **Parent Link**: Connects the artifact to its preceding parent artifact or task description to establish a local chain of custody.
1414

1515
### Schema Shape

reports/phase_15_status.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
## Status Summary
44
- **Phase**: Phase 15: Cryptographic Provenance Engine
5-
- **Status**: success
5+
- **Status**: blocked
66
- **Date**: 2026-06-05
77

88
---
99

1010
## Metadata details
1111
- **PHASE**: Phase 15: Cryptographic Provenance Engine
12-
- **STATUS**: success
12+
- **STATUS**: blocked
1313
- **FILES_CHANGED**:
1414
- `PROJEKT.md`
1515
- `README.md`
@@ -41,7 +41,7 @@
4141
- Pure-Rust algorithm: Built a self-contained SHA-256 implementation to verify offline compatibility, avoiding network socket cargo fetches.
4242
- Review-Gate remain authoritative: Provenance manifests serve as supplementary change-detection metadata rather than formal security proofs.
4343
- **RISKS**: Checksums are used solely as local integrity flags and do not provide absolute certification.
44-
- **NEXT**: Roadmap Completed
44+
- **NEXT**: Review-Gate decided transitions
4545

4646
---
4747

src/cli.rs

Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,12 +1429,64 @@ pub fn sha256_hex(data: &[u8]) -> String {
14291429

14301430
fn handle_verify(file_path: &str, parent: Option<&str>) -> Result<(), String> {
14311431
let path = std::path::Path::new(file_path);
1432+
1433+
// 1. Rejects absolute paths
1434+
if path.is_absolute() {
1435+
return Err(
1436+
"Security Policy Violation: Absolute paths are forbidden in verify command."
1437+
.to_string(),
1438+
);
1439+
}
1440+
1441+
// 2. Reject directory traversal escaping the repository boundary
1442+
let current_dir = std::env::current_dir()
1443+
.map_err(|e| format!("failed to get current working directory: {e}"))?;
1444+
1445+
let canonical_current_dir = current_dir
1446+
.canonicalize()
1447+
.map_err(|e| format!("failed to canonicalize current directory: {e}"))?;
1448+
14321449
if !path.exists() {
14331450
return Err(format!("File '{}' does not exist.", file_path));
14341451
}
14351452

1436-
let content = std::fs::read(path)
1437-
.map_err(|e| format!("failed to read file '{}': {e}", path.display()))?;
1453+
let canonical_path = path
1454+
.canonicalize()
1455+
.map_err(|e| format!("failed to canonicalize file path '{}': {e}", file_path))?;
1456+
1457+
if !canonical_path.starts_with(&canonical_current_dir) {
1458+
return Err(
1459+
"Security Policy Violation: Target path escapes the repository boundary.".to_string(),
1460+
);
1461+
}
1462+
1463+
// 3. Reject forbidden files and directories: .env, .env.*, *.key, *.pem, .git/, .ssh/, .aws/, id_rsa, id_ed25519
1464+
let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
1465+
if file_name == ".env"
1466+
|| file_name.starts_with(".env.")
1467+
|| file_name.ends_with(".key")
1468+
|| file_name.ends_with(".pem")
1469+
|| file_name == "id_rsa"
1470+
|| file_name == "id_ed25519"
1471+
{
1472+
return Err(
1473+
"Security Policy Violation: Accessing secrets or configuration files is forbidden."
1474+
.to_string(),
1475+
);
1476+
}
1477+
1478+
for component in canonical_path.components() {
1479+
if let std::path::Component::Normal(os_str) = component {
1480+
if let Some(s) = os_str.to_str() {
1481+
if s == ".git" || s == ".ssh" || s == ".aws" {
1482+
return Err("Security Policy Violation: Accessing sensitive directories (.git, .ssh, .aws) is forbidden.".to_string());
1483+
}
1484+
}
1485+
}
1486+
}
1487+
1488+
let content = std::fs::read(&canonical_path)
1489+
.map_err(|e| format!("failed to read file '{}': {e}", canonical_path.display()))?;
14381490

14391491
let computed_hash = sha256_hex(&content);
14401492

@@ -2029,36 +2081,88 @@ mod tests {
20292081
));
20302082
}
20312083

2084+
#[test]
2085+
fn test_sha256_standard_vectors() {
2086+
use super::sha256_hex;
2087+
assert_eq!(
2088+
sha256_hex(b""),
2089+
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
2090+
);
2091+
assert_eq!(
2092+
sha256_hex(b"abc"),
2093+
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
2094+
);
2095+
assert_eq!(
2096+
sha256_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
2097+
"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
2098+
);
2099+
}
2100+
20322101
#[test]
20332102
fn test_provenance_verification() {
20342103
use super::handle_verify;
2035-
let temp_dir = std::env::temp_dir();
2036-
let test_file_path = temp_dir.join("test_provenance_artifact.txt");
2037-
let manifest_path = temp_dir.join("test_provenance_artifact.txt.provenance.json");
2104+
2105+
let test_file_path = "test_provenance_artifact_temp.txt";
2106+
let manifest_path = "test_provenance_artifact_temp.txt.provenance.json";
20382107

20392108
// Clean up any leftovers
2040-
let _ = std::fs::remove_file(&test_file_path);
2041-
let _ = std::fs::remove_file(&manifest_path);
2109+
let _ = std::fs::remove_file(test_file_path);
2110+
let _ = std::fs::remove_file(manifest_path);
20422111

20432112
// 1. Write file
2044-
std::fs::write(&test_file_path, "provenance test contents").unwrap();
2113+
std::fs::write(test_file_path, "provenance test contents").unwrap();
20452114

20462115
// 2. Generate manifest
2047-
let gen_res = handle_verify(&test_file_path.to_string_lossy(), Some("parent_task_123"));
2116+
let gen_res = handle_verify(test_file_path, Some("parent_task_123"));
20482117
assert!(gen_res.is_ok());
2049-
assert!(manifest_path.exists());
2118+
assert!(std::path::Path::new(manifest_path).exists());
20502119

20512120
// 3. Verify manifest
2052-
let verify_res = handle_verify(&test_file_path.to_string_lossy(), None);
2121+
let verify_res = handle_verify(test_file_path, None);
20532122
assert!(verify_res.is_ok());
20542123

20552124
// 4. Modify file and verify failure
2056-
std::fs::write(&test_file_path, "provenance test contents MUTATED").unwrap();
2057-
let verify_fail_res = handle_verify(&test_file_path.to_string_lossy(), None);
2125+
std::fs::write(test_file_path, "provenance test contents MUTATED").unwrap();
2126+
let verify_fail_res = handle_verify(test_file_path, None);
20582127
assert!(verify_fail_res.is_err());
20592128

2129+
// 5. Test path safety constraints
2130+
// Rejects absolute path
2131+
let test_abs_path = if cfg!(windows) {
2132+
"C:\\some\\abs\\path"
2133+
} else {
2134+
"/some/abs/path"
2135+
};
2136+
let abs_res = handle_verify(test_abs_path, None);
2137+
assert!(abs_res.is_err());
2138+
assert!(abs_res
2139+
.unwrap_err()
2140+
.contains("Absolute paths are forbidden"));
2141+
2142+
// Rejects secret files (.env)
2143+
// Note: we don't write it, we just check validation rejection logic
2144+
// But since verify check requires file to exist, let's check .env.example or create .env.temp.key
2145+
std::fs::write("test_prov.key", "dummy").unwrap();
2146+
let key_res = handle_verify("test_prov.key", None);
2147+
assert!(key_res.is_err());
2148+
assert!(key_res
2149+
.unwrap_err()
2150+
.contains("Accessing secrets or configuration files is forbidden"));
2151+
let _ = std::fs::remove_file("test_prov.key");
2152+
2153+
// Rejects sensitive directory (.git/config)
2154+
let git_res = handle_verify(".git/config", None);
2155+
assert!(git_res.is_err());
2156+
assert!(git_res
2157+
.unwrap_err()
2158+
.contains("Accessing sensitive directories"));
2159+
2160+
// Rejects directory traversal
2161+
let traverse_res = handle_verify("../outside.txt", None);
2162+
assert!(traverse_res.is_err());
2163+
20602164
// Clean up
2061-
let _ = std::fs::remove_file(&test_file_path);
2062-
let _ = std::fs::remove_file(&manifest_path);
2165+
let _ = std::fs::remove_file(test_file_path);
2166+
let _ = std::fs::remove_file(manifest_path);
20632167
}
20642168
}

0 commit comments

Comments
 (0)