maelys-git
Level 4 — Per-repository policy
Load a repository-local policy through a manifest and verify its digest.Continue from Level 3 — Rich Git context. Keep the declarations and helpers from the earlier levels; this page extends the same wrapper in C and Python.
Level 4 moves the policy from compiled-in source to a repository-local
.maelys/git/ directory. Each repository can carry its own manifest, policy
file, SHA verification, and optional signature. Policy updates become normal
reviewed commits or pull requests, and a tampered policy fails closed at load
time.
Repository layout
.maelys/
git/
manifest.json ← describes the policy: id, domain, SHA, mode
policy.dl ← the Datalog rules
policy.sig ← detached signature (optional, out of scope here)Manifest
Save the Level 1 policy as .maelys/git/policy.dl in UTF-8 with LF endings and one final newline. This manifest uses its exact digest; recompute it whenever the policy bytes change. deny_any is internal and is not a public query.
{
"policy_set_id": "git-gate",
"policy_set_version": "1",
"manifest_version": "1",
"default_profile": "enforce",
"created_for": "maelys-git-tutorial",
"strict_loading": true,
"fail_closed": true,
"capabilities": [],
"policies": [
{
"policy_id": "git_policy.main",
"domain": "git_policy",
"file": "policy.dl",
"sha256": "1ba2ffc80e876eb8b3d8effd6433c538624250fb7f7c16894e549a16b0185d5a",
"mode": "enforce",
"enabled": true,
"description": "Git operation gate",
"queries": [
{
"name": "allow",
"arity": 1
},
{
"name": "deny",
"arity": 2
}
]
}
]
}Loading at startup
static maelys_datalog_status_t load_repo_policy(
const char *manifest_path, maelys_datalog_session_t **session,
maelys_datalog_diagnostic_t *diagnostic)
{
maelys_datalog_policy_t *policy = NULL;
maelys_datalog_status_t rc = maelys_datalog_policy_load_manifest(
manifest_path, MAELYS_DATALOG_PUBLIC_ALLOW_NONE, &policy, diagnostic);
if (rc != MAELYS_DATALOG_STATUS_OK) return rc;
size_t count = 0;
rc = maelys_datalog_policy_count(policy, &count);
if (rc == MAELYS_DATALOG_STATUS_OK && count != 1u)
rc = MAELYS_DATALOG_STATUS_INVALID_FIELD;
if (rc == MAELYS_DATALOG_STATUS_OK)
rc = maelys_datalog_session_create(policy, 0u, session);
(void)maelys_datalog_policy_free(policy);
return rc;
}The manifest loader checks policy.dl against its declared SHA-256. A mismatch
fails loading. The digest does not authenticate a manifest that an attacker
can also replace; protect the package or verify its signature separately.
For Level 3, register git_preds_l3 instead of git_preds and extend
POLICY_ATOMS with GIT_RELEASE_BRANCH, GIT_SENSITIVE_FILE,
GIT_DIRTY_TREE and true. Update the file digest after adding the rules.
Updating the policy
vim .maelys/git/policy.dl
sha256sum .maelys/git/policy.dl | awk '{print $1}' # update sha256 in manifest
vim .maelys/git/manifest.json
git add .maelys/git/
git commit -m "policy: require approval for sensitive files"Policy changes are PRs. The SHA-256 ties the manifest to the exact policy bytes that reviewers approved.
What you have learned
Keep this model
Keep a reviewed policy alongside the repository- Rules live outside the executable
The wrapper loads .maelys/git/policy.dl through a manifest, so policy changes need not be embedded in the wrapper source.
- The digest binds the manifest to the file
The loader checks the policy bytes against the declared SHA-256 before accepting them. A reviewed policy update also requires an updated digest.
- Integrity still depends on trust
An attacker who can replace both the policy and its manifest can choose a matching digest. Protect or authenticate the package through a separate trusted mechanism.