Engagement Report: Ghost Proxy's Classification-Scan Gate Produces a Real Acknowledgement Record

In Ghost Proxy's GRC Command Center, the function behind the classification-scan button was run directly, twice: accepted, it returns a structured, timestamped acknowledgement record. Declined, it returns nothing at all.

Ghost Proxy is a large, private purple-team lab with ten modules, most of them offense-named: Threat Simulator, Neural Lab, Deception Engine, and others that don't belong in a compliance writeup. This report is scoped to one corner of it on purpose: the GRC Command Center, which models enterprise governance work, data classification standards, Just-In-Time admin access, retention policy, SOC 2 mapping, not attack tooling.

Scope and Objective

The target is src/components/GovernanceCenter.tsx's classification-scan action and the shared service it depends on, src/services/warningGate.ts. The repo's own docs/IMPLEMENTATION_PUNCHLIST.md is explicit that GRC Command Center runs on "local policy fixtures only" and does not push policy changes or contact a real scanner. That's an honest scope claim worth taking at face value rather than re-litigating. What it doesn't say outright is whether the one real mechanism sitting underneath every fixture button, the warning-and-acknowledgement gate, actually behaves the way a compliance control should: no record without consent, a structured record when consent is given.

That's the one claim this report checks: does confirmLocalPlanningAction, the function GRC Command Center's INITIATE_CLASSIFICATION_SCAN button calls, produce a real acknowledgement object with a timestamp and a purpose tag when the operator accepts, and produce nothing, no record, no side effect, when they decline.

Findings

The function exists, is called from the real UI code path, and does what the punchlist implies. Reading GovernanceCenter.tsx:

const runClassificationFixture = () => {
  const acknowledged = confirmLocalPlanningAction({
    action: 'Run local classification scan fixture',
    context: 'This opens a classification exercise around the displayed example incident.',
  });
  if (acknowledged) {
    setSelectedCategory('Classification');
    alert('CLASSIFICATION_FIXTURE_READY: Review mapped controls and add real scanner integration from the punchlist.');
  }
};

And the gate itself, in warningGate.ts:

export function confirmLocalPlanningAction(params: { action: string; context: string }): WarningAcknowledgement | null {
  const accepted = confirmWarning({
    title: 'LOCAL PLANNING WARNING',
    action: params.action,
    body: `${params.context}\n\nThis records or displays planning context only. It does not deploy, remediate, scan, or call a provider.`,
  });

  return accepted
    ? {
        confirmed: true,
        acknowledgedAt: Date.now(),
        purpose: 'LOCAL_PLANNING',
        action: params.action,
      }
    : null;
}

That return type, WarningAcknowledgement, is the shape of an access-audit-trail entry: a confirmed flag, a timestamp, a purpose category, the action text, and an optional target. It's the same primitive the Privacy Engine module calls for its own local fixture runs, under a sibling function (confirmLocalFixtureAction), so the mechanism isn't a one-off, it's the shared least-privilege gate for every local-state-changing action across both modules. Reading the code is one thing. Running it is another, so that's what the verification step does.

One honest secondary observation, not a bug: the module's "Just-In-Time Access Fixture" panel shows a static "Elevated Admin (Prod)... Expires in 59:52" line. It's hardcoded text, not a live countdown, no timer backs it. That matches the punchlist's own framing (fixture, not enforcement) and isn't misrepresented anywhere in the UI copy, so it's reported here as confirmation the scope claim holds, not as something broken.

No field-name mismatches, no dead imports, no stale assertions turned up in either file in scope. Nothing here needed fixing.

Verification

Both exported functions were imported directly from the real, unmodified warningGate.ts file (via tsx, no transpile step that rewrites the source) with window.confirm mocked to return true and then false, so the actual acknowledgement logic runs, not a reimplementation of it.

Accepted path, GRC Command Center's classification scan:

=== CASE 1: operator ACCEPTS the classification-scan warning ===
Returned acknowledgement record: {
  "confirmed": true,
  "acknowledgedAt": 1791695840884,
  "purpose": "LOCAL_PLANNING",
  "action": "Run local classification scan fixture"
}
Has timestamp (acknowledgedAt) field: true
Purpose tagged correctly as LOCAL_PLANNING: true

Declined path, same action, same context:

=== CASE 2: operator DECLINES the same warning ===
Returned value on decline: null
No audit record created on decline: true

And the sibling gate behind Privacy Engine's local fixture button, confirming the same primitive holds outside GRC Command Center too:

=== CASE 3: Privacy Engine local fixture, ACCEPTED ===
Returned acknowledgement record: {
  "confirmed": true,
  "acknowledgedAt": 1791695840884,
  "purpose": "LOCAL_FIXTURE",
  "action": "Run local privacy validation fixture: K-Anonymity Processor"
}
Purpose tagged correctly as LOCAL_FIXTURE: true

That's the whole claim, verified end to end: consent produces a structured, timestamped record, and refusal produces none. No fabricated numbers, no assumed behavior, this is the actual function from the actual file, called directly.

Mitigations and Next Steps

  1. The acknowledgement record isn't persisted anywhere yet. The object returned by confirmLocalPlanningAction is real and well-formed, but nothing in GovernanceCenter.tsx currently stores it, it's computed, checked for truthiness, and discarded. For this to function as an actual access-audit trail rather than a one-time gate, the record needs to land somewhere durable, even a local append-only log, before the classification-scan feature can honestly claim audit-trail backing.
  2. The classification scan itself is still a fixture, exactly as the punchlist states. setSelectedCategory('Classification') filters a hardcoded four-row policy list and doesn't inspect any real data store. The punchlist's own "Next Real Builds" section already lists scoped scanner integration as future work, not yet started, nothing to correct there.
  3. The JIT access panel's static expiry text should either tick for real or be labeled as a static example, since "Expires in 59:52" reads like a live session clock at a glance even though the punchlist and the surrounding fixture framing make clear it isn't. Low priority, cosmetic, flagged for whoever does the next pass on this module rather than fixed here, since it touches UI copy decisions outside this report's scope.
  4. The consent-gated, structured-record pattern itself is sound and reusable. It's the right shape for a real governance control once step 1 is addressed, don't rebuild it, extend it.

GhostInThePrompt.com // A warning only earns the word governance once declining it leaves nothing behind and accepting it leaves a record.