Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f25e0ac
test(deploy): require Wardnet Kubernetes manifest path
seonghobae Sep 1, 2026
cec889c
chore: abandon incomplete manifest-path experiment
seonghobae Sep 1, 2026
e06d569
test(deploy): require canonical Wardnet manifest path
seonghobae Sep 1, 2026
10bf291
fix(deploy): move hardened manifest to canonical Wardnet path
seonghobae Sep 1, 2026
3bc9fe7
fix(commercial): publish canonical Wardnet manifest path
seonghobae Sep 1, 2026
57386f0
test(deploy): distinguish migration history from stale manifest refer…
seonghobae Sep 1, 2026
6cd0b16
test(deploy): narrow legacy manifest path exemptions
seonghobae Sep 1, 2026
6ced885
docs: make Wardnet README product-first
seonghobae Sep 1, 2026
e6518b5
docs: add public Pages landing source
seonghobae Sep 1, 2026
b9eb18c
test(deploy): keep stale-path fixture self-scannable
seonghobae Sep 1, 2026
170d906
test(docs): reject broken repository links from Pages landing
seonghobae Sep 1, 2026
deba232
docs: replace unpublished gap-baseline link
seonghobae Sep 1, 2026
4b9869f
docs: remove stale Kubernetes path literal
seonghobae Sep 1, 2026
4ab1e8a
style(tests): apply rustfmt to documentation landing contract
seonghobae Sep 2, 2026
8fd2461
style(tests): apply rustfmt to Kubernetes path contract
seonghobae Sep 2, 2026
a76c641
test(docs): expose unsafe landing-link handling
seonghobae Sep 2, 2026
9616b94
fix(docs): validate landing links within repository
seonghobae Sep 2, 2026
b07e4d2
fix(test): own Kubernetes path scan results
seonghobae Sep 3, 2026
8d656d3
test(deploy): satisfy Rust 1.98 clippy contract
codex Sep 4, 2026
04692b2
merge(main): adopt protected workflow controls for path migration
seonghobae Sep 4, 2026
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Cross-agent conventions for any agent (Claude, Codex, Cursor, opencode, …) wor
- A failing **`trivy-fs` is a REAL finding, not a flake.** Read the job log — it prints each finding's rule id / severity / file — or the run's SARIF results, then **remediate**:
- Rust dependency CVE → bump the crate (`cargo update -p <crate>`, adjust `Cargo.toml`) and commit the updated `Cargo.lock`.
- Container/OS finding → fix the base image or package in the `Dockerfile`.
- k8s/IaC misconfig → fix `deploy/kubernetes/waf-ids-ai-soc.yaml` or `deploy/docker-compose.yml`.
- k8s/IaC misconfig → fix `deploy/kubernetes/wardnet.yaml` or `deploy/docker-compose.yml`.
- Genuine false positive only → add a narrow, commented entry to `.trivyignore` (see the existing `AVD-KSV-0125` note for the expected style). Never broaden it to silence a real vuln.
- Do **not** weaken or disable the gate. A local scan with a stale DB misses findings: run `trivy --download-db-only` first, then scan the **merge ref**, not just the PR head (e.g. `trivy fs --scanners vuln,misconfig --severity CRITICAL,HIGH --ignore-unfixed .`).
- Gating is by the Security Scan **job result**, not the `code_scanning` rule. That org ruleset is intentionally **CodeQL-only** (multiple code-scanning tools can't converge on one PR ref) — do **not** add tools to it.
Expand Down Expand Up @@ -56,4 +56,4 @@ As of 2026-08-04, code-owner review requirements (`require_code_owner_reviews` i
protection, `require_code_owner_review` in rulesets) are disabled across the ContextualWisdomLab
org: there is a single maintainer (solo developer), so a code-owner approval gate can never be
satisfied. This is ON HOLD until the org has multiple maintainers — do NOT re-enable these
settings or add CODEOWNERS-based merge gates before then.
settings or add CODEOWNERS-based merge gates before then.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@

### Operations

- Renamed the hardened Kubernetes deployment asset from `deploy/kubernetes/waf-ids-ai-soc.yaml` to `deploy/kubernetes/wardnet.yaml` without renaming in-cluster resources. Operators and GitOps/package references must use the new repository path; rollback to a pre-migration source revision uses that revision's old path.
- Documented administrator credential provisioning, rotation, rollout verification, rollback, evidence handling, and the boundary with the separate runtime-authentication fail-closed work tracked in issue #78.
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Read in `run_from_env` (`src/lib.rs`): `BIND_ADDR` (default `127.0.0.1:8080`), `
- Audit logs must never leak admin tokens (`scripts/smoke.sh` asserts this).
- Untrusted-input surfaces (request scorer, state deserializer, admin-token parser, DNSBL zone export) are fuzzed; if you change one, keep its libFuzzer target and proptest mirror in sync (`docs/fuzzing.md` lists the invariants per target).
- Block mode is route-scoped; default bind is localhost. See `docs/architecture.md` for security boundaries and the near-term adapter roadmap.
- Deployment assets: `Dockerfile` (two-stage build, pinned base images, runs as non-root `wafids`), `deploy/docker-compose.yml`, `deploy/kubernetes/waf-ids-ai-soc.yaml`.
- Deployment assets: `Dockerfile` (two-stage build, pinned base images, runs as non-root `wafids`), `deploy/docker-compose.yml`, `deploy/kubernetes/wardnet.yaml`.

## Further Docs

Expand Down
292 changes: 155 additions & 137 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/waf-ids-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ pub fn commercial_readiness_snapshot_at(data: &AppData, now_unix: u64) -> Commer
deployment_assets: vec![
"Dockerfile".to_string(),
"deploy/docker-compose.yml".to_string(),
"deploy/kubernetes/waf-ids-ai-soc.yaml".to_string(),
"deploy/kubernetes/wardnet.yaml".to_string(),
Comment thread
seonghobae marked this conversation as resolved.
],
buyer_evidence: vec![
"docs/commercial/20b-krw-sale-readiness.md".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion docs/commercial/buyer-due-diligence.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

- [Dockerfile](../../Dockerfile)
- [Compose stack](../../deploy/docker-compose.yml)
- [Kubernetes manifest](../../deploy/kubernetes/waf-ids-ai-soc.yaml)
- [Kubernetes manifest](../../deploy/kubernetes/wardnet.yaml)

## Buyer Lab Script

Expand Down
4 changes: 3 additions & 1 deletion docs/deployment/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ The Deployment binds `ADMIN_TOKEN` only through that `secretKeyRef` with `option
After the external secret controller reports successful synchronization, apply the complete manifest. Its Namespace object remains in the declarative asset so later applies retain the same ownership boundary:

```bash
kubectl apply -f deploy/kubernetes/waf-ids-ai-soc.yaml
kubectl apply -f deploy/kubernetes/wardnet.yaml
```

The repository path changed from `deploy/kubernetes/waf-ids-ai-soc.yaml` to `deploy/kubernetes/wardnet.yaml`. This is a source-tree/operator path migration only: namespace, Deployment, Service, PVC, image, labels, ports, probes, security context, and Secret coordinates remain unchanged, so applying the renamed file updates the same in-cluster resources. Update scripts, GitOps sources, and packaging rules that referenced the old path before rollout. Rollback to a repository version before this path migration uses that version's old filename; do not create parallel Kubernetes resources as a workaround.

When rotating `ADMIN_TOKEN`, wait for the updated Secret to synchronize, then restart the Deployment because environment-variable-backed Secret values are fixed when a container starts. Verify the rollout and readiness before revoking the previous token:

```bash
Expand Down
4 changes: 2 additions & 2 deletions docs/doctoring/kubernetes-admin-secret-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ kubectl create namespace waf-ids-ai-soc --dry-run=client -o yaml | kubectl apply

The deployment authority must then confirm that its external secret manager/controller has materialized `waf-ids-ai-soc-admin` in that namespace with a non-empty `ADMIN_TOKEN` key. The repository does not prescribe a vendor-specific controller; the integration boundary is the Kubernetes Secret coordinates above.

Apply `deploy/kubernetes/waf-ids-ai-soc.yaml` only after synchronization succeeds. The manifest retains its Namespace object so fresh installs and upgrades converge on the same declarative namespace ownership. Kubernetes resolves the `secretKeyRef` when creating the container. Because the reference is explicitly non-optional, absence of the Secret or key is an operator-visible startup failure instead of an authentication downgrade.
Apply `deploy/kubernetes/wardnet.yaml` only after synchronization succeeds. The repository-path rename does not rename namespace, Deployment, Service, PVC, image, labels, ports, probes, security context, or Secret coordinates. The manifest retains its Namespace object so fresh installs and upgrades converge on the same declarative namespace ownership. Kubernetes resolves the `secretKeyRef` when creating the container. Because the reference is explicitly non-optional, absence of the Secret or key is an operator-visible startup failure instead of an authentication downgrade.

## Rotation

Expand All @@ -48,7 +48,7 @@ If rollout or authentication verification fails, keep or restore the previous cr

## Verification contract

`tests/deployment_manifest.rs` is the permanent regression boundary. It fails if the shipped manifest contains a `kind: Secret` document or the historical placeholder value. It structurally selects Deployment `waf-ids-ai-soc`, scopes the lookup to the `gateway` runtime container, requires exactly one `ADMIN_TOKEN` environment entry, rejects literal fallback values and duplicate `ADMIN_TOKEN` entries, and validates the expected namespace, Secret name, key, and non-optional reference. Decoy Deployments, `initContainers`, comments, duplicate environment entries, literal fallbacks, and `optional: true` cannot satisfy the contract. The same regression suite requires the production guide to bootstrap the namespace before namespaced Secret provisioning.
`tests/deployment_manifest.rs` is the permanent regression boundary. It fails if the shipped manifest contains a `kind: Secret` document or the historical placeholder value. It structurally selects Deployment `waf-ids-ai-soc`, scopes the lookup to the `gateway` runtime container, requires exactly one `ADMIN_TOKEN` environment entry, rejects literal fallback values and duplicate `ADMIN_TOKEN` entries, and validates the expected namespace, Secret name, key, and non-optional reference. Decoy Deployments, `initContainers`, comments, duplicate environment entries, literal fallbacks, and `optional: true` cannot satisfy the contract. The same regression suite requires the production guide to bootstrap the namespace before namespaced Secret provisioning and rejects restoration of the pre-migration Kubernetes repository path.

For release evidence, run the repository's normal formatting, workspace test, Clippy, fuzz, SAST, and Security Scan gates on the exact PR head. A predecessor-head success, skipped required job, or security scan from another merge tree is not evidence for the current artifact.

Expand Down
32 changes: 32 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: Wardnet
---

# Wardnet

Wardnet is a Rust-first gateway and security-operations control plane for governed traffic policy, threat evidence, DNSBL operations, request enforcement, and operator handoff.

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ContextualWisdomLab/wardnet)

## Start here

Use the [repository README](https://github.com/ContextualWisdomLab/wardnet#readme) for the current product boundary, maturity, local quick start, management APIs, deployment guidance, and verification posture. Wardnet deliberately does not present its current source as a complete hardened WAF, IDS, SIEM, or SOAR.

## Product responsibility

Wardnet owns its gateway and SOC control-plane surface: route policy, current local threat and DNSBL evidence, request scoring and enforcement mode, operational evidence, support handoff, and bounded management APIs. Proven external WAF/IDS engines, SIEM and telemetry destinations, threat-intelligence providers, model routing, identity, TLS, secrets, and deployment topology remain independently authoritative.

## Documentation

- [README](https://github.com/ContextualWisdomLab/wardnet#readme) — product overview, quick start, maturity, security and verification.
- [Architecture](https://github.com/ContextualWisdomLab/wardnet/blob/main/docs/architecture.md) — system boundaries and integration responsibilities.
- [Buyer due diligence](https://github.com/ContextualWisdomLab/wardnet/blob/main/docs/commercial/buyer-due-diligence.md) — current buyer-facing evidence surfaces and their verification boundaries.
- [Operations](https://github.com/ContextualWisdomLab/wardnet/tree/main/docs/runbooks) — operator and recovery guidance.
- [Releases](https://github.com/ContextualWisdomLab/wardnet/releases) — immutable release evidence when published.
- [Ask DeepWiki](https://deepwiki.com/ContextualWisdomLab/wardnet) — repository-grounded navigation and questions.

## Evidence boundary

A source version, readiness endpoint, passing test, support bundle, or open pull request is not by itself a production deployment, certification, customer adoption, or published release. Repository-facing claims should remain bound to protected source and the applicable immutable release, deployment, and verification evidence.

This file is a GitHub Pages source prerequisite. Its presence does not mean GitHub Pages is published; publication is complete only after repository settings are reconciled, deployment succeeds, and the live HTTPS site is verified.
12 changes: 11 additions & 1 deletion tests/deployment_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::borrow::Cow;

const MANIFEST: &str = include_str!("../deploy/kubernetes/waf-ids-ai-soc.yaml");
const MANIFEST: &str = include_str!("../deploy/kubernetes/wardnet.yaml");
const PRODUCTION_GUIDE: &str = include_str!("../docs/deployment/production.md");

/// Secret coordinates the gateway Deployment must consume for `ADMIN_TOKEN`.
Expand Down Expand Up @@ -648,3 +648,13 @@ fn fresh_install_bootstraps_namespace_before_secret_provisioning() {
"namespace bootstrap must precede namespaced Secret provisioning"
);
}

#[test]
fn legacy_manifest_path_is_absent() {
let legacy_manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("deploy/kubernetes/waf-ids-ai-soc.yaml");
assert!(
!legacy_manifest.exists(),
"the pre-rename Kubernetes manifest path must not remain in the distributable tree"
);
}
75 changes: 75 additions & 0 deletions tests/documentation_landing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Repository contract for links published by the GitHub Pages landing source.

use std::fs;
use std::path::{Component, Path};

const REPOSITORY_ROOT_README_URL: &str = "https://github.com/ContextualWisdomLab/wardnet#readme";
const REPOSITORY_BLOB_PREFIX: &str = "https://github.com/ContextualWisdomLab/wardnet/blob/main/";
const REPOSITORY_TREE_PREFIX: &str = "https://github.com/ContextualWisdomLab/wardnet/tree/main/";

fn repository_target(target: &str) -> Option<(&str, bool)> {
if target == REPOSITORY_ROOT_README_URL {
Some(("README.md", false))
} else if let Some(relative) = target.strip_prefix(REPOSITORY_BLOB_PREFIX) {
Some((relative, false))
} else if let Some(relative) = target.strip_prefix(REPOSITORY_TREE_PREFIX) {
Some((relative, true))
} else {
None
}
}

fn validated_repository_relative_path(relative: &str) -> &Path {
let path = Path::new(relative);
let stays_inside_repository = !path.is_absolute()
&& path
.components()
.all(|component| matches!(component, Component::Normal(_) | Component::CurDir));
assert!(
stays_inside_repository,
"Pages landing repository link must stay inside repository: {relative}"
);
path
}

#[test]
fn repository_root_readme_target_is_checked() {
assert_eq!(
repository_target(REPOSITORY_ROOT_README_URL),
Some(("README.md", false))
);
}

#[test]
#[should_panic(expected = "must stay inside repository")]
fn repository_link_rejects_parent_escape() {
let _ = validated_repository_relative_path("../outside.md");
}

#[test]
fn pages_landing_repository_links_resolve_in_source_tree() {
let repository = Path::new(env!("CARGO_MANIFEST_DIR"));
let landing = fs::read_to_string(repository.join("docs/index.md"))
.expect("docs/index.md must remain readable as the Pages landing source");

for target in landing
.split("](")
.skip(1)
.filter_map(|candidate| candidate.split(')').next())
{
if let Some((relative, is_directory)) = repository_target(target) {
let candidate = repository.join(validated_repository_relative_path(relative));
if is_directory {
assert!(
candidate.is_dir(),
"Pages landing links to a missing repository directory: {relative}"
);
} else {
assert!(
candidate.is_file(),
"Pages landing links to a missing repository file: {relative}"
);
}
}
}
}
Loading
Loading