Commit Graph
20 Commits
Author SHA1 Message Date
thanhnvandClaude Opus 4.8 e21a1472b1 feat(h5): approval-identity MVP — signed reviewer approvals (C4/V20)
Under CASAN_APPROVAL_STRICT=1, a high-risk approval is trusted ONLY when a
REGISTERED reviewer cryptographically signs THIS exact request and their role is
authorized for the action — a plain env-var CASAN_APPROVER is no longer enough.

- approval-sign.sh: reviewer signs assertion
  "casan-approval|v1|<action>|<actor>|<input_sha256>|<approver_id>" with their key.
- approval-verify.sh: gate looks up reviewer role+pubkey in reviewers.registry,
  enforces role→action authorization, verifies the RSA signature (fail-closed).
- governance-check.sh: strict branch requires a valid signed approval; SoD still
  enforced; default (non-strict) env-var path UNCHANGED (baseline preserved).
- reviewers.registry: role-scoped reviewer identity registry (pubkeys off-repo;
  production replaces with OIDC/JWT from a real IdP).
- phase-h5-approval-tests.sh: 8 checks — valid/authorized approve; unsigned,
  wrong-role, forged-key, unregistered, replay-to-other-request, self-approval
  all denied; non-strict backward-compat.

Baselines: run-casan4 35/35, adversarial 44/44. Lifts H5 policy-approval (C4)
2.5 -> ~3.5-4 / 5. Total suites now 6 (+8 checks = 148).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 23:07:16 +09:00
thanhnvandClaude Opus 4.8 62004c8725 feat(evidence-pack): Plan-09 MVP — casan pack / verify-pack + certified-run gate
evidence-pack.sh {pack|verify-pack} assembles a per-run proof pack from REAL
on-disk logs (summaries only — no raw secret/PII copied; decision-log passes the
data-exfil guard or the pack aborts). Produces the standard set: run-summary,
h1..h7 reports, redteam-result, benign-fp-report, artifact-manifest, decision-log,
plus a signed manifest head (evidence-pack.sig).
Tamper-evident: verify-pack recomputes every file hash vs artifact-manifest.json
(any change fails) and verifies the RSA signature over manifest-head.txt (a
manifest re-forge fails without the off-repo key).
Certified run: run-summary.certified is true ONLY when required gates pass
(H4 exercised, H5 audit chain valid, H5 telemetry verified, no unresolved cost
spike, benign-FP within budget) and none was silently skipped — missing evidence
records an honest reason and does NOT certify.
CLI mapping: `casan pack <id>` -> evidence-pack.sh pack; `casan verify-pack <id>`
-> evidence-pack.sh verify-pack. phase3-evidence-pack-tests.sh covers it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:02:39 +09:00
thanhnvandClaude Opus 4.8 dbeb8e89d2 feat(track-c-mvp): C3 data-exfil guard + C6 sandbox scaffold + Phase 2 suite
C3 (V19) data-exfil-guard.sh: destination-aware egress checkpoint built on the
  H4 detectors. cloud/artifact boundaries fail closed on any secret; audit
  boundary emits a PII-masked copy (fail closed on an unmaskable secret).
  Covers secret-to-cloud, artifact-leaks-env, and PII-in-audit.
C6 (V22) sandbox-run.sh: static policy pre-check (BLOCK on reading ~/.ssh/creds,
  network egress, fork bomb, writes outside workspace, huge-file/disk-fill) plus
  ulimit file-size/CPU backstops and the wall-clock timeout. HONEST SCOPE: this
  is not kernel isolation — the production target (docker --network=none
  --read-only --pids-limit / nsjail) is documented as TODO(C6-prod). Process cap
  is opt-in so it never breaks legitimate commands on a busy host.
phase2-track-c-tests.sh: 29 adversarial checks (C1 13, C2 6, C3 4, C6 6).
  Baselines preserved: run-casan4 35/35, adversarial 44/44.
  Running total: 35 + 44 + 25 + 29 = 133 checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:57:09 +09:00
thanhnvandClaude Opus 4.8 c51e0f88a3 feat(track-c-mvp): C1 action gating + C2 supply-chain gate
C1 (V17) action-gate.sh: gates the ACTION, not just the tool name. Outcome model
  ALLOW/WARN/REQUIRE_APPROVAL/BLOCK. BLOCK on sensitive-file writes (.env, *.pem,
  id_rsa, .github/workflows, .ssh, .aws/credentials, .npmrc) and destructive/
  remote-exec commands (rm -rf /, curl|bash, chmod 777, git push --force);
  REQUIRE_APPROVAL on dependency installs and non-local network egress (clears
  only with an audited CASAN_ACTION_APPROVER). Decisions logged to action-gate.jsonl.
C2 (V18) supply-chain-gate.sh + supply-chain-scan.py: diffs package.json /
  requirements.txt / pom.xml / build.gradle against a baseline (explicit or git
  HEAD). BLOCK on denylisted/known-malicious packages, typosquats (edit-distance 1
  to a known package), and dangerous lifecycle scripts (pre/post/install);
  REQUIRE_APPROVAL on any new dependency. Emits a dep-diff report and records
  which live scanners (npm audit / pip-audit / osv-scanner) are available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:51:43 +09:00
thanhnvandClaude Opus 4.8 cf2c42b9fa feat(a6-tests): Plan-07 Track A — A6 benign/FP budget + Phase 1 adversarial suite
A6: benign-corpus (EN 32 / VI 32 / JA 31 = 95 legit inputs) + redteam-vectors.jsonl
  (12 vectors, 4 critical). benign-fp-report.sh runs the REAL security-check over
  both and enforces the budget: false_positive_rate <= 3%, adversarial block >= 95%,
  CRITICAL block = 100%. Measured on the deterministic layer (semantic OFF) so it
  is reproducible in CI. Current: FP=0.00%, block=100%, critical=100%.
phase1-track-a-tests.sh: 25 adversarial checks covering A1–A6 (homoglyph, zero-width,
  fullwidth, base64/hex, strict fail-closed, tool-output scan, telemetry tamper,
  cost slow-boil/spray/cold-start, FP budget). Kept separate so baseline suites
  (35/35, 44/44) stay untouched. New Phase 1 total: 35 + 44 + 25 = 104 checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:46:33 +09:00
thanhnvandClaude Opus 4.8 7e998f67c2 feat(h5-h6-hardening): Plan-07 Track A — A4 telemetry integrity, A5 cost controls
A4 (V9): telemetry-integrity.sh binds provider-usage.jsonl + cost/metrics.jsonl
  to a signed manifest head. Tampering a token flips the head (MISMATCH); an
  attacker who rewrites the head cannot re-sign it (SIGNATURE_INVALID) without
  the off-repo key. sign-audit-head.sh now also signs telemetry (best-effort).
  Supports CASAN_AUDIT_PRIV/PUB overrides for self-contained verification.
A5 (V12/V13/V14): cost-spike-detect.sh adds an absolute per-call cap
  (CASAN_COST_ABSOLUTE_MAX_TOKENS, enforced from record #1 → catches slow-boil
  and cold-start) and a cumulative budget (CASAN_COST_CUMULATIVE_BUDGET_TOKENS →
  catches under-threshold spray), keeping the existing median×mult spike test.
  Backward compatible: <3 records with no caps still exits 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:25:00 +09:00
thanhnvandClaude Opus 4.8 ac40c0b281 feat(h4-hardening): Plan-07 Track A — A1 strict semantic, A2 unicode/encoding, A3 tool-output scan
A1 (V1): CASAN_SECURITY_STRICT=1 makes semantic classification REQUIRED and
  fail-closed — model unavailable/no-verdict → BLOCK, never a silent SKIP.
  Non-strict CASAN_SEMANTIC_CLASSIFY=1 keeps regex verdict but logs
  SEMANTIC_SKIPPED loudly (sourced casan-log.sh). Default (no flags) unchanged.
A2 (V3/V4): unicode-normalize.py (NFKC + zero-width strip + Cyrillic/Greek
  homoglyph fold) and decode-suspicious.py (base64/hex decode + rescan, printable
  filter to avoid false positives) feed new match_either/secret_match haystacks.
  Blocks homoglyph, zero-width, fullwidth, base64/hex-smuggled injection & secrets.
A3 (V7): tool-output-scan.sh scans tool output for injection/secret before it
  re-enters model context; wrapper runs it after H6-exec (mode off|warn|block,
  strict→block). warn is default to preserve benign-draft behaviour.

Baseline preserved: run-casan4 35/35, adversarial 44/44.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:20:50 +09:00
thanhnvandClaude Opus 4.8 392f190b7e feat(model): implement OpenAI/Anthropic cloud backends + cloud-aware judge gate
The cloud branch of model-call.py was a stub (cloud_backend_not_implemented,
failed even with a key set); casan-step.mjs gated the judge on a hard-coded
Ollama ping. Wire up the real cloud path so a model can run without Ollama.

- model-call.py: add call_openai() and call_anthropic() (raw urllib, no new
  dependency — matches the existing call_ollama). Endpoints hard-pinned to the
  SSRF allowlist; keys read from env, never logged. Anthropic sends no
  temperature/thinking (rejected as 400 on Opus 4.8/4.7; omitting thinking
  keeps the terse one-word classify/judge answer). main() routes by
  ollama:/openai:/anthropic: prefix; key-unset still fails closed honestly.
  provider-usage.jsonl cost_source is per-backend, keeping ollama's exact
  "ollama_local_real_tokens" tag that evidence/tests key on.
- casan-step.mjs: ollamaAvailable() -> modelAvailable() — when
  CASAN_MODEL_PRIMARY is a cloud spec with its key set, the judge runs through
  the cloud path; otherwise it pings local Ollama as before. Default
  (unset CASAN_MODEL_PRIMARY) is unchanged.
- CASAN_MASTER_RUNBOOK.md: update sections 0/1/4/7/8 — cloud is now
  implemented (not a stub); keep the honest "untested with a real key" +
  CA-cert caveats.

Not verified against a live API key (none available); confirmed key-set makes
a real HTTPS call and key-unset fails closed. Gates unchanged:
security-gate PASS=11 FAIL=0, adversarial PASS=44 FAIL=0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:38:59 +09:00
thanhnvandClaude Opus 4.8 1b61d7f381 feat(log): unified CASAN_LOG_LEVEL across Boss, agent step, harness wrapper
Add a shared log taxonomy (error<warn<info<debug<trace, default info) wired
through all three layers, plus a machine-readable per-step run log.

- scripts/casan-log.mjs + .specify/scripts/bash/casan-log.sh: shared logger
  ([LEVEL] ts [component] msg), stderr-only so stdout contracts and exit codes
  are byte-identical. Node side includes trace-level redaction (secret/PII).
- run-casan-pipeline.mjs: per-STEP info line, loop-activation warnings
  (BACK-TO-PLAN, FAIL->STEP6), debug harness rc + trace_id, trace payload
  excerpt, end-of-run 13-STEP summary table, and one JSONL line/step in
  .specify/logs/pipeline-run.jsonl. New --dry-run stubs all agents but keeps
  the real wrapper in the loop (deterministic, offline, full 13 STEP + loops).
- casan-step.mjs: debug logs for judge verdict, checkpoint, rollback; logger
  import degrades to noop when the file is copied standalone (T1 test).
- casan-harness.sh: debug-log each phase H4-in -> H5 -> [H2-gate] -> H6-exec
  -> H4-out with its rc; optional CASAN_PHASE_REPORT JSON for the Boss.

Default level (info) keeps output close to before; behavior opt-in via env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 10:04:55 +09:00
thanhnvandClaude Opus 4.8 82011a0c67 fix(ci): eliminate audit-signature flake + self-heal deploy docker access
Two comprehensive CI fixes:

1. Adversarial harness flake (PASS=42 FAIL=2, state-dependent):
   governance-check.sh re-exported audit-public.pem only when GENERATING a
   new signing key. On a CI runner where the off-repo private key PERSISTS
   across runs, the checked-out (Vault-signed) audit-public.pem drifted out
   of sync with the local re-signing key, so verify-audit-chain.sh rejected a
   genuine head ("H5/H2 verifies the genuine signed chain"). Now always
   re-export the public key matching the signing key — mirrors the same fix
   already applied to tool-audit-lib.sh (c31987c). Reproduced the exact
   FAIL=2 locally with a drifted persisted key; now deterministically 44/44.

2. Deploy docker.sock permission denied:
   Added a self-healing preflight to deploy-okr that ensures the deploy user
   is in the docker group on the web VPS (idempotent, passwordless sudo) and
   proves a fresh SSH session can reach the daemon before streaming images.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:44:20 +09:00
thanhnvandClaude Sonnet 4.6 c31987caa2 fix(tool-audit-lib): always re-export local pubkey to audit-public.pem
Previously the public key was only written on first key generation.
If a CI step (sign-audit-head.sh via Vault KMS) overwrote audit-public.pem
after the key was generated, subsequent calls to append_tool_audit signed
with the local key while audit-public.pem held the Vault key — causing
verify-tool-audit.sh to fail with signature mismatch.

Now the public key is re-exported on every call so audit-public.pem always
matches the private key used to sign tool-calls-head.sig.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 21:37:13 +09:00
thanhnvandClaude Sonnet 4.6 3e7690fe37 fix(security-gate): resolve tool-calls audit signature key mismatch
Root cause: tool-audit-lib.sh signs tool-calls-head.sig with a local RSA
key (~/.casan/audit-keys/), but sign-audit-head.sh (called as a CI step)
overwrites audit-public.pem with the Vault KMS public key. On the second
run inside security-gate.sh, the local key still exists so audit-public.pem
is NOT updated, leaving a Vault key vs local-key mismatch that causes
verify-tool-audit.sh to exit 1.

Fix 1 — sign-audit-head.sh: after signing the audit.jsonl chain via Vault
KMS, also re-sign the tool-calls chain head with the same casan-audit-key.
Both chains are now anchored to the same Vault public key in audit-public.pem.

Fix 2 — run-casan4-harness-tests.sh: call sign-audit-head.sh just before
the inline verify-tool-audit.sh check (line 217). This re-signs both chains
with Vault KMS so the inline check sees anchor=signed instead of mismatched
local key vs Vault pub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 20:07:14 +09:00
thanhnv 4bcc66d9db fix(vault-kms): pass pubkey response via tmpfile not string literal
Embedding $response as Python triple-quoted string caused json.loads()
to fail with 'Invalid control character' when Vault's JSON contained
\n sequences in the PEM public key (bash \n → Python newline → invalid
JSON control char).

Fix: write response to mktemp, pass path as argv, read with open().
2026-07-01 18:36:11 +09:00
thanhnvandClaude Sonnet 4.6 f6cc723bf4 fix(ci): security-gate auto-installs frontend deps if vitest missing
Instead of skipping the frontend test when vitest is not installed,
security-gate.sh now runs `npm ci -w frontend` automatically.

Also add `cache: "npm"` to security-gate's actions/setup-node so the
npm cache from the frontend-tests job is reused — prevents OOM on
the 1GB VPS (cache restore is disk-only, not 300MB download).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 17:27:09 +09:00
thanhnvandClaude Sonnet 4.6 b0864a59d7 fix(ci): security-gate skips frontend tests when vitest not installed
security-gate.sh checked only `command -v node` before running
`npm test -w frontend`. In the CI security-gate job, node is in PATH
(from actions/setup-node) but root node_modules are NOT installed
(npm ci was removed to prevent OOM). This caused the frontend test to
fail with "Cannot find module vitest".

Fix: also require node_modules/.bin/vitest to exist. Without it the
step SKIPs gracefully — frontend tests are already covered by the
dedicated frontend-tests CI job which runs first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 17:24:57 +09:00
thanhnvandClaude Sonnet 4.6 9892e82221 feat(wave5): CI/CD pipeline + Vault KMS + OKR deploy to port 80/3001
Infrastructure (H3 CI gate, H5 KMS):
- Gitea Actions enabled (GITEA__actions__ENABLED=true)
- act_runner: Docker-outside-of-Docker for deploy job
- Vault Transit RSA-2048 signing keys (casan-audit-key, casan-policy-key)

Vault KMS scripts (H5 governance):
- .specify/scripts/bash/vault-kms.sh — sign/verify/pubkey/ensure-key
- .specify/scripts/bash/sign-audit-head.sh — sign audit chain via Vault
- Updated sign-policy-bundle.sh — Vault path + local fallback
- Updated security-gate.sh — KMS gate added (PASS=11 FAIL=0)

OKR app deployment (port 80/3001):
- Dockerfile.backend — node:22-slim (node:sqlite requires Node 22)
- Dockerfile.frontend — node:20-alpine build + nginx:alpine runtime
- nginx/nginx.conf — React SPA + /api/v1/* proxy to okr-backend:3001
- backend/entrypoint.sh — auto init DB on first run + seed
- .dockerignore

CI pipeline (.gitea/workflows/ci.yml):
- Job 1: Vitest frontend tests (H3)
- Job 2: CASAN security gate + Vault KMS signing (H4/H5)
- Job 3: Deploy OKR → port 80 (runs on push to main after tests pass)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 12:55:17 +09:00
thanhnvandClaude Sonnet 4.6 a8edbea534 fix T1+T4: wire rollback into pipeline; real Ollama telemetry in metrics
T1 (H7): casan-step.mjs now calls rollback-manager.sh checkpoint before
overwriting plan.md at attempt-2, writes tx-id to plan.checkpoint.txid
sidecar, and executes rollback on REJECTED verdict. rollback-transactions.jsonl
records a real cp restore command. Adversarial test: checkpoint exists,
real cp command recorded, plan hash matches pre-overwrite content.

T4 (H6): casan-harness.sh exports CASAN_STEP_NAME=$ACTION_NAME before
agent-metrics.sh so nested model calls (model-call.py) and the provider-
cost-lookup.py query share the same step label. metrics.jsonl now writes
cost_source=provider_telemetry instead of word_count_estimate when a real
Ollama call is made within the same step. Adversarial test: verified with
CASAN_STEP_NAME=t4-telemetry-test end-to-end.

adversarial-harness-tests.sh: 40 → 44 PASS / 0 FAIL (+3 T1, +1 T4)
security-gate.sh: PASS=10 FAIL=0 SKIP=0 (verified, local ornith:9b)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 11:25:50 +09:00
Nam Pham Dinh ThanhandClaude Sonnet 4.6 838b2473b6 Wave 4: frontend Vitest tests, H1/H7 fixes, Windows compat (python3→python, MSYS2 path)
WV4-A: Added 16 Vitest/RTL tests to frontend (jsdom env, fail-before proof verified)
WV4-B: Created 12 stub traces for pipeline retention gap; fixed MSYS2/Python path mismatch in context-validate.sh; run-casan4-harness-tests.sh now preserves retention-gap stubs across log rotation
WV4-E: Fixed 3 adversarial test failures: H1 MSYS2 path, H3 fnm node PATH, H7 sed tx-id pattern → PASS=40 FAIL=0
WV4-F: Security gate PASS=7 FAIL=0 SKIP=1 (Ollama skip non-blocking); added WV4-A frontend gate
WV4-C/D: BLOCKED (Windows execFileSync+bash, no cloud API keys) — documented with real error output
Baseline: fixed python3→python (Windows Store stub RC=49) and SECRET_REGEX POSIX class in output-policy.yaml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 02:23:51 +09:00
thanhnv eaf919e744 Update optimize wave3 (need update wave 4 to wave 8) 2026-07-01 00:02:30 +09:00
thanhnv 07ac1bdcdd update first - 84 2026-06-30 02:21:39 +09:00