ci: add pilot v1 hardening guards

This commit is contained in:
igor04091968
2026-06-12 11:25:57 +03:00
parent 106d796d95
commit fd5c788569
9 changed files with 165 additions and 6 deletions
+6
View File
@@ -23,6 +23,12 @@ jobs:
- name: Run production inventory placeholder guard self-test
run: bash scripts/check_production_inventory_placeholders.sh --self-test
- name: Run private-config guard
run: bash scripts/check_private_config_guard.sh
- name: Run portal contract sync guard
run: node scripts/check_portal_contract_sync.mjs
rust-runtime-guard:
runs-on: ubuntu-latest
steps:
+31
View File
@@ -0,0 +1,31 @@
name: rust-workspace
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
rust-workspace:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust 1.85
uses: dtolnay/rust-toolchain@1.85.0
with:
components: rustfmt, clippy
- name: Format
run: cargo fmt --manifest-path adk-rust/Cargo.toml --all -- --check
- name: Test
run: cargo test --manifest-path adk-rust/Cargo.toml --workspace
- name: Clippy
run: cargo clippy --manifest-path adk-rust/Cargo.toml --workspace --all-targets -- -D warnings
- name: Release build
run: cargo build --manifest-path adk-rust/Cargo.toml --workspace --release
+6 -2
View File
@@ -1,7 +1,11 @@
# Local secrets
/secrets/
/private-config/*.env
/private-config/*.local
/private-config/*
!/private-config/
!/private-config/README.md
!/private-config/.gitkeep
!/private-config/*.example
!/private-config/*.template
/ansible/inventory.ini
/codex_history.txt
-4
View File
@@ -1,4 +0,0 @@
*.env
*.local
!.gitignore
!*.example
+1
View File
@@ -0,0 +1 @@
+13
View File
@@ -0,0 +1,13 @@
# private-config
This directory is reserved for local, host-specific, or secret configuration.
Do not commit real runtime values here. Git only allows:
- `private-config/README.md`
- `private-config/.gitkeep`
- `private-config/*.example`
- `private-config/*.template`
Use `scripts/check_private_config_guard.sh` before commits and in CI to verify
that no private config file has entered the git index.
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, "..");
const contractPath = path.join(
root,
"adk-rust/crates/detmir-portal/src/contracts/openapi.json",
);
const requiredPublicPaths = [
"/api/contracts",
"/api/contracts/openapi.json",
"/api/contracts/typescript.d.ts",
"/api/reports",
"/api/executive",
"/api/workforce",
"/api/security",
"/api/forensics",
"/api/ueba",
"/api/pfsense",
"/api/incidents",
"/api/cases",
"/api/readiness/latest",
"/api/readiness/bundle",
"/api/readiness/verify",
];
function fail(message, details = []) {
console.error(message);
for (const detail of details) {
console.error(`- ${detail}`);
}
process.exit(1);
}
let contract;
try {
contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
} catch (error) {
fail(`failed to read OpenAPI contract: ${contractPath}`, [error.message]);
}
if (!contract || typeof contract !== "object" || !contract.paths || typeof contract.paths !== "object") {
fail("OpenAPI contract has no object 'paths' section.");
}
const contractPaths = Object.keys(contract.paths);
const forbiddenPaths = contractPaths.filter((contractPathName) =>
/dioxus|prototype-mirror|mirror/i.test(contractPathName),
);
if (forbiddenPaths.length > 0) {
fail("OpenAPI contract contains legacy/prototype paths.", forbiddenPaths);
}
const effectivePublicPaths = new Set();
for (const contractPathName of contractPaths) {
effectivePublicPaths.add(contractPathName);
if (contractPathName.startsWith("/") && !contractPathName.startsWith("/api/")) {
effectivePublicPaths.add(`/api${contractPathName}`);
}
}
const missingPaths = requiredPublicPaths.filter((requiredPath) => !effectivePublicPaths.has(requiredPath));
if (missingPaths.length > 0) {
fail("OpenAPI contract is missing required public API paths.", missingPaths);
}
console.log("portal contract sync guard: OK");
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
violations=()
while IFS= read -r -d '' path; do
rest="${path#private-config/}"
case "$path" in
private-config/README.md|private-config/.gitkeep)
continue
;;
esac
if [[ "$rest" != */* && ( "$rest" == *.example || "$rest" == *.template ) ]]; then
continue
fi
violations+=("$path")
done < <(git ls-files -z -- private-config)
if (( ${#violations[@]} > 0 )); then
printf 'private-config guard failed: tracked private files are forbidden. Allowed files are README.md, .gitkeep, *.example, *.template.\\n' >&2
printf '%s\\n' "${violations[@]}" >&2
exit 1
fi
echo "private-config guard: OK"
+11
View File
@@ -7,6 +7,16 @@ cd "$ROOT_DIR"
TARGET_ROOT="${CARGO_TARGET_DIR:-$ROOT_DIR/adk-rust/target}"
RUST_BIN="${QUALITY_GATE_RUST:-}"
echo "[preflight] Private-config guard"
bash scripts/check_private_config_guard.sh
echo "[preflight] Portal contract sync guard"
if command -v node >/dev/null 2>&1; then
node scripts/check_portal_contract_sync.mjs
else
echo "node not found, skipping portal contract sync guard."
fi
rust_candidates=()
if [[ -n "$RUST_BIN" ]]; then
rust_candidates+=("$RUST_BIN")
@@ -39,6 +49,7 @@ fi
echo "[3/6] Node syntax check (if node available)"
if command -v node >/dev/null 2>&1; then
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
node --check scripts/check_portal_contract_sync.mjs >/dev/null
else
echo "node not found, skipping."
fi