Commit ae7532a
feat(scripts): hcg-policy-smoke.sh — stealth-profile status canary (Phase E) (#224)
## Summary
Extends the §1.5 operator pre-check (`scripts/hcg-policy-smoke.sh`) with
four exact-status stealth-profile canaries that pin the deny *status
code*: internal+stealth routes (`sdp-status-get`, `cartridge-load-post`)
must return exactly **404** (capability existence hidden), and
authenticated routes without a `stealth_profile` (`status-get`,
`cartridges-list-get`) must return exactly **403** (capability exists,
caller lacks credentials). `probe()` gains a three-digit literal pattern
(`[1-5][0-9][0-9]`) to support the assertion; existing `deny` and
`allow_or_upstream` patterns are unchanged.
## Why
The existing `deny` pattern accepts any 4xx. A misconfiguration where
`:stealth_profiles` is not populated at runtime (the runtime config that
`application.ex configure_stealth/1` sets from `policy.stealth.enabled:
true`) would silently demote internal+stealth routes from 404 to bare
403. The deny probes would still pass — but the security property would
be broken: untrusted callers could now distinguish "route exists, you
can't access it" (403) from "route does not exist" (404), leaking
capability existence. This is the exact threat the `sdp-status-get` rule
narrative calls out (`"not confirmable from outside"`).
The two code paths fixed by this canary are both in
`http-capability-gateway/lib/http_capability_gateway/gateway.ex`:
- `handle_denial/3` returns `stealth_profiles["default"][trust_str]` for
rules with `stealth_profile: "default"` and bare 403 otherwise.
- A regression that drops the rule's `stealth_profile` field or that
nils-out the application env would surface here.
The pre-check now catches both.
## Changes
- `scripts/hcg-policy-smoke.sh`:
- `probe()` gains a `[1-5][0-9][0-9]` exact-status case clause.
- Four new probes between the existing verb-canary block and the
unknown-path canary: two internal+stealth (expect 404), two
authenticated (expect 403).
- Top-of-file docstring updated to mention the new class alongside the
verb / path canaries.
- `docs/integration/hcg-tier2-rollout-runbook.md`: §1.5 acceptance prose
updated to describe the new canary class; version 0.6 → 0.7; date
2026-06-14 → 2026-06-15.
## Test plan
- [x] `bash -n scripts/hcg-policy-smoke.sh` — script parses cleanly.
- [x] `bash scripts/hcg-policy-smoke.sh --help` — usage still rendered
correctly (the new probes do not change the option surface).
- [ ] Operator-side: when the gateway is up in staging,
`scripts/hcg-policy-smoke.sh --gateway-url <staging>` reports four new
`PASS` lines (`stealth-canary:GET /sdp/status (internal+stealth → 404)`,
…) alongside the existing 25 deny probes, six verb canaries, and one
path canary. A `FAIL` on the 404-expected probes specifically means
stealth is not armed — investigate `:stealth_profiles` before flipping
the §1.5 checkbox.
## Phase E channel notes
- Channel: `hyperpolymath/standards#91` (parent) → Phase E (`#100`) is
the active sub-issue.
- This PR is a Phase E §1.5 sub-task; per rollout runbook §6.5
(single-lane channel discipline), Phase E close is owner-driven and
gated on §3.3 (100% soak), §6.4 (Trustfile flip), and cerro-torre `.ctp`
signing. Each Phase E sub-task therefore lands as a `Refs`-only advance
— this PR deliberately does **not** `Closes #100`.
Refs hyperpolymath/standards#91
Refs hyperpolymath/standards#100
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
_Generated by [Claude
Code](https://claude.ai/code/session_015gHmHcGoRXocbo1N77662D)_
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent c710e25 commit ae7532a
2 files changed
Lines changed: 44 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | | - | |
10 | | - | |
11 | | - | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
| 94 | + | |
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | | - | |
14 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
15 | 18 | | |
16 | 19 | | |
17 | 20 | | |
| |||
152 | 155 | | |
153 | 156 | | |
154 | 157 | | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
155 | 171 | | |
156 | 172 | | |
157 | 173 | | |
| |||
224 | 240 | | |
225 | 241 | | |
226 | 242 | | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
227 | 264 | | |
228 | 265 | | |
229 | 266 | | |
| |||
0 commit comments