# IB Node Security Audit

> This report audits Integration Broker node user accounts for security issues that go beyond authentication configuration (which is covered by the N...

---

Site [llms.txt](/llms.txt)

---

<div class="alert alert-primary border shadow-sm mb-4 psl-prospect-cta" role="note">
  <div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3">
    <div>
      <strong>New to psLens?</strong> This page documents one specific report. To see how it runs in the product, what
      the output looks like, and how teams use it in practice, start with a live walkthrough.
    </div>
    <div class="d-flex flex-wrap gap-2">
      <a class="btn btn-sm btn-primary" href="/contact/">Book a Demo</a>
      <a class="btn btn-sm btn-outline-primary" href="/docs/reports/">Browse Reports</a>
    </div>
  </div>
</div><div id="pslens-context-panel" class="card border-info mb-4 d-none">
  <div class="card-header bg-light text-info py-2 fw-bold d-flex align-items-center border-bottom border-info-subtle">
    <i class="bi bi-info-circle-fill me-2"></i>
    <span>Tailored Operational Context</span>
  </div>
  <div class="card-body p-0">
    <ul class="list-group list-group-flush">
      <li id="row-db" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Target Database:</strong>
        <span id="ctx-db" class="badge bg-secondary font-monospace">&mdash;</span>
      </li>
      <li id="row-type" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Context Type:</strong>
        <span id="ctx-type" class="badge bg-light text-dark border font-monospace text-uppercase">&mdash;</span>
      </li>
      <li id="row-severity" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Alert Severity:</strong>
        <span id="ctx-severity" class="badge">&mdash;</span>
      </li>
      <li id="row-time" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Triggered Time:</strong>
        <span id="ctx-time" class="text-muted small">&mdash;</span>
      </li>
      <li id="row-details" class="list-group-item py-2 d-none">
        <strong id="label-details" class="d-block mb-1">Firing Context:</strong>
        <code id="ctx-details" class="d-block p-2 bg-light border rounded small"
          style="white-space: pre-wrap; word-break: break-all;">&mdash;</code>
      </li>
      <li id="row-sql" class="list-group-item py-2 d-none">
        <div class="d-flex justify-content-between align-items-center mb-1">
          <strong id="label-sql">Contextual SQL:</strong>
          <button id="btn-copy-sql" type="button" class="btn btn-sm btn-outline-secondary py-0 px-2 small">
            <i class="bi bi-clipboard me-1"></i>Copy SQL
          </button>
        </div>
        <pre class="mb-0 p-2 bg-light border rounded small"><code id="ctx-sql" style="white-space: pre-wrap; word-break: break-word;">&mdash;</code></pre>
      </li>
    </ul>
  </div>
</div>

<script>
  (function () {
    const params = new URLSearchParams(window.location.search);
    const metadata = params.get('metadata');
    if (!metadata) return;

    try {
      
      const base64 = metadata.replace(/-/g, '+').replace(/_/g, '/');
      const jsonStr = decodeURIComponent(escape(window.atob(base64)));
      const data = JSON.parse(jsonStr);

      if (data) {
        let hasData = false;

        if (data.db) {
          document.getElementById('ctx-db').textContent = data.db;
          document.getElementById('row-db').classList.remove('d-none');
          hasData = true;
        }

        if (data.type) {
          document.getElementById('ctx-type').textContent = data.type;
          document.getElementById('row-type').classList.remove('d-none');
          hasData = true;
        }

        if (data.severity) {
          const severityBadge = document.getElementById('ctx-severity');
          const severity = data.severity.toLowerCase();
          severityBadge.textContent = severity.toUpperCase();
          if (severity === 'critical') {
            severityBadge.className = 'badge bg-danger';
          } else if (severity === 'warning') {
            severityBadge.className = 'badge bg-warning text-dark';
          } else {
            severityBadge.className = 'badge bg-info';
          }
          document.getElementById('row-severity').classList.remove('d-none');
          hasData = true;
        }

        if (data.t) {
          const date = new Date(data.t * 1000);
          document.getElementById('ctx-time').textContent = date.toLocaleString();
          document.getElementById('row-time').classList.remove('d-none');
          hasData = true;
        }

        if (data.details) {
          document.getElementById('ctx-details').textContent = data.details;

          
          const labelDetails = document.getElementById('label-details');
          if (data.type === 'object') {
            labelDetails.textContent = 'Object Metadata Details:';
          } else if (data.type === 'report') {
            labelDetails.textContent = 'Report Description:';
          } else {
            labelDetails.textContent = 'Firing Context:';
          }

          document.getElementById('row-details').classList.remove('d-none');
          hasData = true;
        }

        if (data.sql) {
          document.getElementById('ctx-sql').textContent = data.sql;
          document.getElementById('row-sql').classList.remove('d-none');
          const copyBtn = document.getElementById('btn-copy-sql');
          if (copyBtn && navigator.clipboard) {
            copyBtn.addEventListener('click', function () {
              navigator.clipboard.writeText(data.sql).then(function () {
                copyBtn.innerHTML = '<i class="bi bi-check2 me-1"></i>Copied';
                setTimeout(function () {
                  copyBtn.innerHTML = '<i class="bi bi-clipboard me-1"></i>Copy SQL';
                }, 2000);
              });
            });
          }
          hasData = true;
        }

        if (hasData) {
          document.getElementById('pslens-context-panel').classList.remove('d-none');
        }
      }
    } catch (e) {
      console.error('Failed to parse operational context metadata:', e);
    }
  })();
</script>

## IB Node Security Audit Report

**Report ID:** `ib-node-security-audit`
**Category:** Integration Broker

## Purpose

This report audits Integration Broker node user accounts for security issues that go beyond authentication configuration (which is covered by the [Nodes with No Password](/docs/reports/security/security-nodes-no-password/) report). It checks for elevated user privileges on nodes, shared user accounts across multiple nodes, and nodes with no active routings.

## What It Detects

### CRITICAL — Anonymous Node User Analysis

Checks the ANONYMOUS node's associated PeopleSoft user account. Flags as HIGH RISK if:

- The user account is **unlocked** (can log into PIA directly)
- The user has **PeopleTools access** (Application Designer, Data Mover, etc.)

The ANONYMOUS node handles unauthenticated IB traffic. Its user should have minimal privileges and a locked account.

### WARNING — Shared Node Users

Identifies cases where the same PeopleSoft User ID (OPRID) is configured on multiple active nodes. Each node should have its own distinct service account for:

- **Audit trail** — Knowing which node performed an action
- **Security isolation** — Revoking one node's access without affecting others
- **Least privilege** — Tailoring permissions per node's specific needs

### WARNING — Active Nodes with No Active Routings

Finds active non-local nodes that have no active routings in PSIBRTNGDEFN. These nodes may be:

- Leftover from decommissioned integrations
- Candidates for deactivation to reduce attack surface
- Covered only by wildcard (`~~ANY~~`) routings (reported separately)

### WARNING — Node Users with PeopleTools Access

Identifies node service accounts whose permission lists grant access to PeopleTools clients (Application Designer, Data Mover, Query, etc.). The check covers the user's primary permission list and every permission list granted through the user's roles. Node accounts should never need development tool access.

## Tables Queried

### PSMSGNODEDEFN — Message Node Definitions

|    Field    |                 Description                 |            Values            |
| ----------- | ------------------------------------------- | ---------------------------- |
| MSGNODENAME | Node name (primary key)                     |                              |
| ACTIVE_NODE | Whether the node is active                  | `1` = Active, `0` = Inactive |
| LOCALNODE   | Whether this is a local node                | `1` = Local, `0` = External  |
| USERID      | PeopleSoft user ID associated with the node |                              |
| NODE_TYPE   | Node type                                   |                              |

### PSIBRTNGDEFN — Routing Definitions

|      Field       |     Description     |            Values            |
| ---------------- | ------------------- | ---------------------------- |
| SENDERNODENAME   | Sending node name   | Node name or `~~ANY~~`       |
| RECEIVERNODENAME | Receiving node name |                              |
| EFF_STATUS       | Effective status    | `A` = Active, `I` = Inactive |

### PSOPRDEFN — User Definitions

|  Field   |       Description       |            Values            |
| -------- | ----------------------- | ---------------------------- |
| OPRID    | User ID (primary key)   |                              |
| OPRCLASS | Primary permission list |                              |
| ACCTLOCK | Account lock status     | `0` = Unlocked, `1` = Locked |

### PSROLEUSER / PSROLECLASS — Role Permission Lists

Used to find every permission list a node user holds through roles. PeopleTools access is usually granted through a role, not the primary permission list.

### PSAUTHITEM — Menu/Tools Authorizations

Used to check if a permission list grants PeopleTools client access (APPLICATION_DESIGNER, DATA_MOVER, etc.).

## Data Flow

```text
1. Fetch ALL message nodes from PSMSGNODEDEFN
   (batches of 300)
        |
        v
2. Fetch ALL active routings from PSIBRTNGDEFN
   Build set of node names with active routings
        |
        v
3. For each unique UserID on active nodes:
   Look up user in PSOPRDEFN and the user's
   role permission lists (PSROLEUSER -> PSROLECLASS)
        |
        v
4. For each unique permission list found (primary + roles):
   Check PSAUTHITEM for PeopleTools access
        |
        v
5. Analyze and categorize findings:
   - Anonymous node user privileges
   - Shared node users (same OPRID on 2+ nodes)
   - Active nodes not in routing coverage set
   - Node users with PeopleTools access
        |
        v
6. Generate Markdown report grouped by severity
```

## Interpreting Results

- **CRITICAL findings on the ANONYMOUS node** indicate that unauthenticated IB traffic is processed under a user with elevated privileges. This is a significant security risk.
- **Shared node users** increase blast radius if one account is compromised and make it harder to trace which node performed specific actions.
- **Nodes with no routings** represent unnecessary attack surface. If a node isn't routing any messages, it should be deactivated.
- **PeopleTools access on node accounts** means a compromised integration could potentially be used to modify PeopleSoft objects.

## Recommendations

1. Lock the ANONYMOUS node user account to prevent direct PIA login
2. Remove PeopleTools access from node service accounts (their roles and primary permission list)
3. Create distinct service accounts for each Integration Broker node
4. Deactivate nodes with no active routings if they are no longer needed
