Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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: 7 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ jobs:
run: |
make test

- name: Run Auth Privilege Boundary Regression
run: |
gcc -std=c99 -Wall -Wextra -I. -I./include -I./core \
tests/test_auth_privilege_boundary.c -L. -lqihse \
-o tests/test_auth_privilege_boundary
LD_LIBRARY_PATH=$PWD ./tests/test_auth_privilege_boundary

- name: Run Python SDK Tests
run: |
export LD_LIBRARY_PATH=$PWD
Expand Down
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# QIHSE Repository Rules

These rules are architectural invariants, not suggestions. A code path that violates them is incorrect even if the feature works, benchmarks well, or existing tests pass. Existing violations are defects and must not be copied into new code.

## Security invariants

### 1. No classified read primitive without a user/security context

Any primitive capable of materializing, enumerating, searching, streaming, exporting, serializing, snapshotting, backing up, iterating over, or otherwise disclosing data that may carry classification or SCI metadata MUST accept or inherit an explicit authenticated security context (`qihse_user_t *` or an equivalent typed context).

Protocol, SDK, compatibility, export, backup, iterator, and internal adapter layers MUST propagate that identity to the lowest data-retrieval layer. They MUST NOT fall back to a context-free/raw read primitive for classified-capable data.

`NULL` MUST NOT accidentally become an authorization bypass. If QIHSE supports an explicitly security-disabled operating mode, that mode must be represented deliberately in configuration/context state rather than inferred from a forgotten user argument.

### 2. No principal may create or modify a principal above itself

A principal MUST NOT create, promote, or modify another principal to a privilege level greater than its own.

This applies to role, classification level, SCI compartments, account-management capability, authentication bypasses, hardware-token policy, delegated user-creation capability, and any future privilege-bearing field.

`can_create_users` delegates account creation only. It does not delegate Operator authority and MUST NOT permit creation or promotion of a principal above the creator.

### 3. Every new protocol adapter requires a low-clearance/high-data negative test

Every new protocol, wire-format adapter, compatibility layer, or externally reachable data-access surface MUST include an automated negative authorization test before merge.

The test MUST authenticate or construct a low-clearance principal, place or reference data above that principal's clearance and/or outside its SCI compartments, attempt access through the new adapter, and assert denial with no protected payload disclosure.

Where the adapter exposes them, the test SHOULD cover both the normal query path and bypass-prone forms such as direct-ID lookup, enumeration, export, bulk read, snapshot/backup, iterator, or raw compatibility commands.

The negative test MUST run in CI. A successful happy-path test is not a substitute.

## Merge rule

Changes that violate any invariant above are merge blockers. Performance, compatibility, internal-only deployment, prototype status, or convenience are not exceptions. If a new capability cannot preserve an invariant yet, keep it behind an explicitly insecure/development-only boundary rather than weakening the invariant silently.
8 changes: 8 additions & 0 deletions core/qihse_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ qihse_user_t* qihse_auth_create_user(qihse_user_t* creator, uint32_t user_id, ui
pthread_mutex_unlock(&auth_mutex);
return NULL;
}

// Role numbers are ordered from most to least privileged (OPERATOR=0).
// Delegating account creation must never permit privilege escalation.
if (role < creator->role) {
Comment thread
SWORDIntel marked this conversation as resolved.
Outdated
Comment thread
SWORDIntel marked this conversation as resolved.
Outdated
Comment thread
SWORDIntel marked this conversation as resolved.
Outdated
qihse_audit_log("USER_CREATE_DENIED_ROLE", creator->user_id, user_id, classif, sci);
pthread_mutex_unlock(&auth_mutex);
return NULL;
}

// Ensure the creator possesses the clearance they are attempting to grant
if (classif > creator->classification_level) {
Expand Down
80 changes: 80 additions & 0 deletions tests/test_auth_privilege_boundary.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <assert.h>
#include <stdio.h>

#include "qihse_auth.h"

int main(void) {
qihse_auth_init();

qihse_user_t* operator_user = qihse_auth_get_user(0);
assert(operator_user != NULL);

/* User creation is intentionally blocked until the seeded operator password is rotated. */
assert(qihse_auth_modify_user(operator_user, 0, NULL, "SecureOpPass1!", -1, -1));

qihse_user_t* analyst = qihse_auth_create_user(
operator_user,
1,
QIHSE_ROLE_ANALYST,
5,
0x0003,
"AnalystPass123!",
false
);
assert(analyst != NULL);

/* Delegate account creation without delegating Operator authority. */
assert(qihse_auth_modify_user(operator_user, 1, NULL, NULL, -1, 1));
assert(analyst->can_create_users);

/* Regression: a delegated Analyst must never be able to mint an Operator. */
qihse_user_t* escalated_operator = qihse_auth_create_user(
analyst,
2,
QIHSE_ROLE_OPERATOR,
5,
0x0001,
"EscalatePass123!",
false
);
assert(escalated_operator == NULL);

/* Same-level/subordinate creation remains valid when clearance and SCI are subsets. */
qihse_user_t* peer_analyst = qihse_auth_create_user(
analyst,
3,
QIHSE_ROLE_ANALYST,
5,
0x0001,
"PeerAnalyst123!",
false
);
assert(peer_analyst != NULL);

qihse_user_t* guest = qihse_auth_create_user(
operator_user,
4,
QIHSE_ROLE_GUEST,
0,
0,
"GuestAccount123!",
false
);
assert(guest != NULL);
assert(qihse_auth_modify_user(operator_user, 4, NULL, NULL, -1, 1));

/* The invariant is generic: a delegated Guest cannot create an Analyst either. */
qihse_user_t* escalated_analyst = qihse_auth_create_user(
guest,
5,
QIHSE_ROLE_ANALYST,
0,
0,
"GuestEscalate123!",
false
);
assert(escalated_analyst == NULL);

puts("auth privilege-boundary regression tests passed");
return 0;
}
Loading