Skip to content

fix: propagate SANDBOX_NAME to telegram bridge and resolve openshell path - #222

Merged
ericksoa merged 6 commits into
NVIDIA:mainfrom
brianwtaylor:fix/service-sandbox-env
Mar 18, 2026
Merged

fix: propagate SANDBOX_NAME to telegram bridge and resolve openshell path#222
ericksoa merged 6 commits into
NVIDIA:mainfrom
brianwtaylor:fix/service-sandbox-env

Conversation

@brianwtaylor

@brianwtaylor brianwtaylor commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #198
Fixes #199

Test plan

Automated Tests

npm test

Manual Testing

  • Run NEMOCLAW_SANDBOX=my-sandbox ./scripts/start-services.sh
  • Verify the telegram bridge receives the correct sandbox name
  • Remove openshell from PATH and verify the bridge finds it via fallback paths
  • Check bridge logs for correct sandbox name and openshell path

Hardware Validation

Path resolution logic validated on DGX Spark:

Machine command -v openshell Fallback paths Expected behavior
spark-c231 Not found None present resolveOpenshell() exits with clear error — correct
spark1 Not found None present Same — correct

openshell is installed during nemoclaw onboard (typically to ~/.local/bin/), so it is expected to be absent on a pre-onboard system. The fallback chain (~/.local/bin/usr/local/bin/usr/bin) covers all standard installation locations. The absolute-path check (startsWith("/")) prevents alias injection in non-interactive shells.

Summary by CodeRabbit

  • New Features

    • Improved detection and validation of the required shell executable with explicit failure when not found.
    • Sandbox name handling now respects existing environment variables and only injects a sandbox context when valid.
    • Telegram bridge startup passes the resolved sandbox context so the bridge runs in the correct sandbox.
  • Tests

    • Added tests for sandbox name expansion, environment override behavior, and shell-executable resolution and precedence.

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>
Reject non-absolute paths from command -v (e.g. aliases or functions)
and fall through to the explicit candidate list.

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>
start-services.sh unconditionally set SANDBOX_NAME to "default",
ignoring any exported value. nemoclaw start also never passed the
sandbox name from the registry. Together these caused the telegram
bridge to target the wrong sandbox.

- Preserve existing SANDBOX_NAME in start-services.sh before defaulting
- Pass the registry's default sandbox from nemoclaw start

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>
Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>
@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aea1b362-7b96-49ce-80af-d7b985a5de78

📥 Commits

Reviewing files that changed from the base of the PR and between 8be5329 and 689b4ee.

📒 Files selected for processing (2)
  • bin/lib/resolve-openshell.js
  • test/service-env.test.js

📝 Walkthrough

Walkthrough

start now reads registry.listSandboxes() to obtain a default sandbox and conditionally injects SANDBOX_NAME when spawning start-services.sh. start-services.sh prefers an existing SANDBOX_NAME or falls back to "default" and forwards it to service invocations. telegram-bridge resolves the openshell binary path (or exits) instead of relying on PATH. Tests and a resolve-openshell utility were added.

Changes

Cohort / File(s) Summary
Startup / sandbox propagation
bin/nemoclaw.js, scripts/start-services.sh
bin/nemoclaw.js reads registry.listSandboxes() to choose a default sandbox and conditionally prefixes SANDBOX_NAME when spawning start-services.sh. start-services.sh defers to an existing SANDBOX_NAME (or "default") and passes it into service starts (Telegram bridge).
OpenShell resolution & bridge
bin/lib/resolve-openshell.js, scripts/telegram-bridge.js
New resolveOpenshell() utility returns an absolute openshell path (with test hooks); telegram-bridge.js initializes OPENSHELL via that resolver, exits if unresolved, and invokes openshell via the resolved absolute path.
Tests & manifest
test/service-env.test.js, package.json
Adds tests for openshell resolution fallbacks and SANDBOX_NAME / NEMOCLAW_SANDBOX expansion/override behaviors; small package.json manifest updates.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant NM as nemoclaw.js
    participant Start as start-services.sh
    participant Bridge as telegram-bridge.js
    participant Open as openshell

    User->>NM: npm start
    NM->>NM: registry.listSandboxes()
    NM->>Start: spawn start-services.sh (prefix SANDBOX_NAME if valid)
    Start->>Start: SANDBOX_NAME = ${SANDBOX_NAME:-default}
    Start->>Bridge: start telegram-bridge with SANDBOX_NAME env
    Bridge->>Bridge: OPENSHELL = resolveOpenshell()
    alt OPENSHELL found
        Bridge->>Open: exec OPENSHELL sandbox ssh-config ...
        Open-->>Bridge: ssh-config output
    else not found
        Bridge-->>Bridge: exit with error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through PATHs both near and far,
I found the shell beneath the star.
Sandbox names now tag along,
Start to bridge — the links are strong.
🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures both main changes: propagating SANDBOX_NAME to the telegram bridge and resolving the openshell path, matching the core issues being fixed.
Linked Issues check ✅ Passed The pull request addresses all coding requirements from both linked issues: propagates SANDBOX_NAME to telegram bridge subprocess [#198], implements resolveOpenshell with absolute path validation and fallback paths [#199].
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing #198 and #199: SANDBOX_NAME propagation, openshell resolution, related test coverage, and manifest updates—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
test/service-env.test.js (2)

7-8: Remove unused imports.

fs and path are imported but not used in this test file.

🧹 Proposed fix
 const { describe, it } = require("node:test");
 const assert = require("node:assert/strict");
 const { execSync } = require("child_process");
-const fs = require("fs");
-const path = require("path");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/service-env.test.js` around lines 7 - 8, Remove the unused imports by
deleting the require statements for fs and path (the top-level const fs =
require("fs"); and const path = require("path");) from the test file so there
are no unused variables; run the tests/linter to confirm no references remain
and commit the cleanup.

14-35: Good test double, but consider testing the actual production function directly.

The resolveOpenshellTestable function mirrors the production logic in telegram-bridge.js, but if the production code changes, this test double might drift out of sync. Consider either:

  1. Exporting resolveOpenshell from telegram-bridge.js for direct testing, or
  2. Adding an integration test that exercises the real function in a controlled environment.

That said, the current approach is pragmatic given that importing telegram-bridge.js causes immediate side effects (module-level resolveOpenshell() call and potential process.exit).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/service-env.test.js` around lines 14 - 35, The test defines a local
duplicate resolveOpenshellTestable that can drift from the real resolveOpenshell
in telegram-bridge.js; instead either export the real resolveOpenshell from
telegram-bridge.js and update tests to call that exported function, or add an
integration test that spawns/loading telegram-bridge.js in a controlled
environment (mocking environment variables, PATH and filesystem and
capturing/neutralizing the module-level resolveOpenshell() call and any
process.exit) so the production logic is exercised directly; reference the
function name resolveOpenshell (and the test double resolveOpenshellTestable)
when making the change.
bin/nemoclaw.js (1)

137-139: Consider sanitizing or validating the sandbox name before shell interpolation.

If defaultSandbox contains shell metacharacters (e.g., spaces, quotes, $, backticks), the constructed command could break or be exploited. While sandbox names are typically controlled, defensive quoting or validation would be safer.

♻️ Suggested improvement
 async function start() {
   await ensureApiKey();
   const { defaultSandbox } = registry.listSandboxes();
-  const sandboxEnv = defaultSandbox ? `SANDBOX_NAME="${defaultSandbox}"` : "";
+  // Validate sandbox name contains only safe characters
+  const safeName = defaultSandbox && /^[a-zA-Z0-9_-]+$/.test(defaultSandbox) ? defaultSandbox : null;
+  const sandboxEnv = safeName ? `SANDBOX_NAME="${safeName}"` : "";
   run(`${sandboxEnv} bash "${SCRIPTS}/start-services.sh"`);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/nemoclaw.js` around lines 137 - 139, The command construction uses
defaultSandbox from registry.listSandboxes() and interpolates it into sandboxEnv
passed to run(), which can break or be exploited if it contains shell
metacharacters; before building sandboxEnv and calling run(`${sandboxEnv} bash
"${SCRIPTS}/start-services.sh"`), validate or sanitize defaultSandbox (e.g.,
allow only a safe pattern like [A-Za-z0-9._-], reject or normalize others) or
perform proper shell-escaping/quoting of the value so it cannot inject
additional commands; update the code around defaultSandbox, sandboxEnv and the
run() invocation to use the validated/escaped value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/nemoclaw.js`:
- Around line 137-139: The command construction uses defaultSandbox from
registry.listSandboxes() and interpolates it into sandboxEnv passed to run(),
which can break or be exploited if it contains shell metacharacters; before
building sandboxEnv and calling run(`${sandboxEnv} bash
"${SCRIPTS}/start-services.sh"`), validate or sanitize defaultSandbox (e.g.,
allow only a safe pattern like [A-Za-z0-9._-], reject or normalize others) or
perform proper shell-escaping/quoting of the value so it cannot inject
additional commands; update the code around defaultSandbox, sandboxEnv and the
run() invocation to use the validated/escaped value.

In `@test/service-env.test.js`:
- Around line 7-8: Remove the unused imports by deleting the require statements
for fs and path (the top-level const fs = require("fs"); and const path =
require("path");) from the test file so there are no unused variables; run the
tests/linter to confirm no references remain and commit the cleanup.
- Around line 14-35: The test defines a local duplicate resolveOpenshellTestable
that can drift from the real resolveOpenshell in telegram-bridge.js; instead
either export the real resolveOpenshell from telegram-bridge.js and update tests
to call that exported function, or add an integration test that spawns/loading
telegram-bridge.js in a controlled environment (mocking environment variables,
PATH and filesystem and capturing/neutralizing the module-level
resolveOpenshell() call and any process.exit) so the production logic is
exercised directly; reference the function name resolveOpenshell (and the test
double resolveOpenshellTestable) when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 92beba4d-c565-4a01-b983-a0f8b2d4b9cd

📥 Commits

Reviewing files that changed from the base of the PR and between 2a9afbc and 78b31be.

📒 Files selected for processing (4)
  • bin/nemoclaw.js
  • scripts/start-services.sh
  • scripts/telegram-bridge.js
  • test/service-env.test.js

- Remove unused fs/path imports from test file
- Extract resolveOpenshell to shared module with DI for direct testing
- Validate sandbox name before shell interpolation

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/resolve-openshell.js`:
- Around line 20-41: The code sets home = opts.home || process.env.HOME ||
"/tmp", which can make the candidates list use /tmp and allow path-hijack;
change the logic in resolve-openshell to avoid falling back to "/tmp": prefer
opts.home, then require('os').homedir() / process.env.HOME, and if no real home
is available omit or skip the user-local candidate
`${home}/.local/bin/openshell` when building the candidates array; update
references to the home variable and the candidates construction (and any
tests/mock that set opts.home) so the user-local path is only added when home is
a valid, non-world-writable user home.

In `@test/service-env.test.js`:
- Around line 85-108: The tests call execSync without providing an explicit env,
so they can inherit NEMOCLAW_SANDBOX or SANDBOX_NAME from the parent and are
flaky and miss verifying export to child processes; update each failing case
that invokes execSync (the three uses shown) to pass a hermetic env object
(e.g., Object.assign({}, process.env, { NEMOCLAW_SANDBOX: ..., SANDBOX_NAME: ...
}) or {} with only the vars you need) via the execSync options.env parameter,
and extend each shell command to spawn a nested subprocess to assert the
variable is exported (for example run the expansion then run a nested bash -c
'echo $SANDBOX_NAME' and assert that nested output matches expected). Ensure you
update the three execSync calls referenced and assert the nested-child output,
not just the parent-shell expansion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b77fde36-0603-4676-8cb7-35ef83f377f6

📥 Commits

Reviewing files that changed from the base of the PR and between 78b31be and 8be5329.

📒 Files selected for processing (4)
  • bin/lib/resolve-openshell.js
  • bin/nemoclaw.js
  • scripts/telegram-bridge.js
  • test/service-env.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/telegram-bridge.js
  • bin/nemoclaw.js

Comment thread bin/lib/resolve-openshell.js Outdated
Comment thread test/service-env.test.js
Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

@ericksoa ericksoa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — thorough fix for the env var propagation issue, plus the resolveOpenshell() hardening is a nice security bonus. Great test coverage. Thanks @brianwtaylor!

@ericksoa
ericksoa merged commit 3cb192c into NVIDIA:main Mar 18, 2026
3 checks passed
Ryuketsukami pushed a commit to Ryuketsukami/NemoClaw that referenced this pull request Mar 24, 2026
…path (NVIDIA#222)

* fix: propagate sandbox name to bridge and resolve openshell path

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* fix: validate resolveOpenshell returns absolute path

Reject non-absolute paths from command -v (e.g. aliases or functions)
and fall through to the explicit candidate list.

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* fix: pass default sandbox name from registry to start-services.sh

start-services.sh unconditionally set SANDBOX_NAME to "default",
ignoring any exported value. nemoclaw start also never passed the
sandbox name from the registry. Together these caused the telegram
bridge to target the wrong sandbox.

- Preserve existing SANDBOX_NAME in start-services.sh before defaulting
- Pass the registry's default sandbox from nemoclaw start

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* test: add resolveOpenshell and SANDBOX_NAME defaulting tests

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* fix: address CodeRabbit review comments

- Remove unused fs/path imports from test file
- Extract resolveOpenshell to shared module with DI for direct testing
- Validate sandbox name before shell interpolation

* fix: harden HOME fallback and make SANDBOX_NAME tests hermetic

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

---------

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>
jessesanford pushed a commit to jessesanford/NemoClaw that referenced this pull request Mar 24, 2026
…path (NVIDIA#222)

* fix: propagate sandbox name to bridge and resolve openshell path

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* fix: validate resolveOpenshell returns absolute path

Reject non-absolute paths from command -v (e.g. aliases or functions)
and fall through to the explicit candidate list.

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* fix: pass default sandbox name from registry to start-services.sh

start-services.sh unconditionally set SANDBOX_NAME to "default",
ignoring any exported value. nemoclaw start also never passed the
sandbox name from the registry. Together these caused the telegram
bridge to target the wrong sandbox.

- Preserve existing SANDBOX_NAME in start-services.sh before defaulting
- Pass the registry's default sandbox from nemoclaw start

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* test: add resolveOpenshell and SANDBOX_NAME defaulting tests

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

* fix: address CodeRabbit review comments

- Remove unused fs/path imports from test file
- Extract resolveOpenshell to shared module with DI for direct testing
- Validate sandbox name before shell interpolation

* fix: harden HOME fallback and make SANDBOX_NAME tests hermetic

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>

---------

Signed-off-by: Brian Taylor <brian.taylor818@gmail.com>
mafueee pushed a commit to mafueee/NemoClaw that referenced this pull request Mar 28, 2026
…#222)

* feat(policy): add policy recommendation plumbing — denial aggregation, transport, approval pipeline, and mechanistic recommendations

Implement the infrastructure layer for automated policy recommendations (NVIDIA#204):

- Proto: 9 new RPCs and messages for draft policy lifecycle (submit, get, approve, reject, approve-all, edit, undo, clear, history)
- Persistence: SQLite/Postgres migrations and store methods for draft_policy_chunks and denial_summaries tables
- Server: Full gRPC handler implementations with mechanistic mapper that auto-generates NetworkPolicyRule proposals from denial summaries
- Sandbox: DenialAggregator with MPSC channel, deduplication, periodic flush to gateway via SubmitPolicyAnalysis
- CLI: 'openshell draft' subcommand with get/approve/reject/approve-all/undo/clear/history operations
- TUI: Draft recommendations panel accessible from sandbox policy view
- Docs: Architecture documentation in architecture/policy-advisor.md

* feat(policy): add L7-aware mechanistic mapper and policy advisor CTF example

Add L7 rule generation to mechanistic mapper (build_l7_rules,
generalise_path, looks_like_id) with 3 new unit tests. Add
examples/policy-advisor/ with a 7-gate CTF script, restrictive
sandbox policy, and walkthrough README.

* fix(policy): use sandbox name for denial flush and add TUI draft badges

Fix denial aggregator passing sandbox UUID instead of name to
SubmitPolicyAnalysis, which caused 'sandbox not found' errors on
flush. Add notification badges to the TUI sandbox list and detail
header showing pending draft recommendation counts.

* fix(policy): deduplicate draft chunks and tolerate overlapping OPA rules

Skip draft chunk creation when a pending/approved chunk already covers
the same host:port endpoint, preventing duplicate rules across denial
aggregator flush cycles.

Rewrite three OPA complete rules (network_policy_for_request,
matched_network_policy, matched_endpoint_config) to tolerate multiple
matching policies without triggering a "complete rule conflict" error.
network_policy_for_request becomes a boolean, matched_network_policy
uses a set comprehension with min(), and matched_endpoint_config uses
an array comprehension with index-0 selection.

* feat(tui): interactive draft actions, highlight bar, and detail popup

Rework the draft recommendations panel to match the logs UX:
- Highlight bar (green accent + background) instead of arrow marker
- Viewport-aware j/k scrolling with g/G for top/bottom
- Enter opens a full-screen detail popup showing endpoints, binaries,
  rationale, security notes, and action hints

Add approve/reject/approve-all draft actions:
- [a] approve selected chunk, [x] reject, [A] approve all pending
- Actions work from both the list view and the detail popup
- gRPC calls run async; result updates status bar and refreshes data
- Nav bar shows all available keybindings

Fix draft count refresh: sandbox_draft_counts now refreshes on every
tick (not just Dashboard), so the detail header badge updates in
real time.

Improve badge labels: show 'N pending' instead of a bare number in
both the dashboard sandbox list and sandbox detail header.

* refactor(policy): DB-level draft chunk dedup with hit counter and timestamps

Replace the in-memory HashSet dedup in SubmitPolicyAnalysis with a
database-level upsert. New denormalized columns on draft_policy_chunks:
- host, port: extracted from proposed_rule at insert time
- hit_count: incremented on conflict (same sandbox + host + port)
- first_seen_ms, last_seen_ms: track when the endpoint was first and
  most recently proposed

A partial unique index (WHERE status IN ('pending','approved')) ensures
only one active chunk per endpoint per sandbox; rejected/superseded
chunks don't block new proposals.

Surface hit_count and first/last_seen in:
- CLI: 'openshell draft get' shows 'Hits: N (first ..., last ...)'
- TUI: detail popup shows hits row; list view shows 'Nx' suffix

* fix(policy): optimistic retry on policy version conflicts + structured logging

merge_chunk_into_policy and remove_chunk_from_policy now retry up to 5
times on UNIQUE constraint violations (version conflicts from concurrent
approvals). Each attempt re-reads the latest policy, re-merges the rule,
and increments the version. This eliminates the race condition where
rapid successive approvals would fail with a DB error.

Add structured tracing to all draft action handlers:
- ApproveDraftChunk: logs rule_name, host, port, hit_count before merge
  and version + policy_hash after success
- RejectDraftChunk: logs rule_name, host, port, reason
- ApproveAllDraftChunks: logs pending_count at start, per-chunk merge
  progress, and final summary with chunks_approved/skipped
- UndoDraftChunk: logs before/after with rule_name and version
- Retry attempts log as warnings with attempt number and conflicting
  version

* wip: forward proxy fix, mapper allowed_ips, TUI polish, CTF rewrite

* fix(tui): use correct --gateway flag for ssh-proxy ProxyCommand

* chore: add Docker cleanup script for stale images, volumes, and build cache

* feat(tui): approve-all confirmation modal and CTF cleanup

Add [A] confirmation popup that snapshots pending chunks, shows a
scrollable list, and approves each chunk individually on confirm.
This prevents approving chunks that arrived after the modal opened.

Remove transient issue NVIDIA#205 reference from CTF victory banner.

* fix(tui): correct import ordering for rustfmt

* wip: stateful toggle model, rename to network rules

Draft chunks now follow a toggle state machine:
  pending -> approved | rejected (initial decision)
  approved <-> rejected (toggle)

One row per (sandbox_id, host, port) via expanded unique index.
Rejecting an approved rule removes it from the active policy.
Re-approving a rejected rule merges it back.

Rename CLI from 'draft' to 'rule', TUI from 'Draft Recommendations'
to 'Network Rules'. State-aware keybindings: approved shows [x] Revoke,
rejected shows [a] Approve. Fix sandbox detail hiding delete confirmation
behind pending message.

* refactor(policy): move mapper sandbox-side, slim schema, per-binary granularity

Move mechanistic mapper from gateway to sandbox so all analysis runs
sandbox-side (N sandboxes = N independent pipelines). Gateway is now a
thin validate + persist + approval layer.

Architectural changes:
- Move mechanistic_mapper.rs from navigator-server to navigator-sandbox
- Sandbox flush flow: aggregator drains -> mapper runs -> proposals sent
- Gateway SubmitPolicyAnalysis: validate + persist only, no mapper
- Drop denial_summaries table (write-only, zero readers)
- Consolidate migrations 003+004+005 into single 003

Schema slimming:
- Drop 5 unused columns from draft_policy_chunks (stage, denial_refs,
  supersedes_chunk_id, analysis_mode, decided_by)
- Add per-binary granularity: binary column, widen unique index to
  (sandbox_id, host, port, binary)
- Mapper groups by (host, port, binary), one proposal per triple
- Merge appends binary to existing rule; revoke removes just that binary

CTF & UX:
- 7-gate CTF: add Gate 3 (curl -> ifconfig.me:80) for per-binary demo
- TUI shows binary short name in list, full path in detail popup
- CLI output shows binary field
- Idempotent rule names, hit_count accumulates real denial counts
- Rationale text no longer bakes in stale denial count
@wscurran wscurran added the bug-fix PR fixes a bug or regression label Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug-fix PR fixes a bug or regression

Projects

None yet

3 participants