← Back to Skills

Builder Logs — Redaction, Rotation & Admin API

The iec-builder runs under PM2 on the builder host and writes structured pino logs to `~/.pm2/logs/iec-builder-out.log` and `-error.log`. Because pino-http…

Ruledeploy

About

The iec-builder runs under PM2 on the builder host and writes structured pino logs to `~/.pm2/logs/iec-builder-out.log` and `-error.log`. Because pino-http logs request metadata, log lines historically contained plaintext `Authorization: Bearer <JWT>` tokens. Three layers now keep those logs safe and bounded.

Skill Content

This is the raw markdown that gets installed as a Claude Code rule.

# Builder Logs — Redaction, Rotation & Admin API

The iec-builder runs under PM2 on the builder host and writes structured pino logs to
`~/.pm2/logs/iec-builder-out.log` and `-error.log`. Because pino-http logs request metadata,
log lines historically contained plaintext `Authorization: Bearer <JWT>` tokens. Three layers
now keep those logs safe and bounded.

## 1. Write-time redaction (pino `redact`)

The pino logger (`src/utils/logger.ts`) redacts secret-bearing paths from every record before
it is written, censoring with `[REDACTED]`:

- Request headers: `req.headers.authorization`, `req.headers.cookie`,
  `req.headers["x-internal-key"]`, `req.headers["x-api-key"]`, `req.headers["set-cookie"]`
- Response headers: `res.headers["set-cookie"]`
- Wildcard fields: `*.authorization`, `*.password`, `*.secret`, `*.token`
- Named secrets: `BIO_CLIENT_SECRET`, `CONFIG_ENCRYPTION_KEY`

This prevents new tokens from ever being written.

## 2. Server-side rotation (pm2-logrotate)

The builder host runs `pm2-logrotate` so logs never grow unbounded:

| Setting | Value |
|---------|-------|
| `max_size` | `50M` |
| `retain` | `14` |
| `compress` | `true` (gzip) |
| `rotateInterval` | `0 0 * * *` (daily at midnight) |
| `workerInterval` | `30` (seconds) |

Rotated archives are named `iec-builder-{out,error}__<timestamp>.log.gz` and live alongside the
live logs in `~/.pm2/logs`. The pre-existing 762 MB log was scrubbed of Bearer tokens before the
first compression so no archive leaks secrets.

## 3. Read-time redaction + platform-admin Logs API

Historical lines (written before layer 1 existed) still contain tokens, so every line surfaced
through the API is **redacted again at read time** via regex
(`Bearer <token>`, `"authorization":"…"`, `"x-internal-key":"…"`, `"cookie":"…"`).

Both endpoints live in `src/routes/platform.ts` and require **super_admin or service** role
(tawa-web uses `X-Internal-Key`). They read from the same host the builder runs on.

### `GET /platform/logs`

Returns the live log inventory, rotated archives, and rotation status.

```jsonc
{
  "success": true,
  "data": {
    "files": [
      { "stream": "out", "path": "...", "sizeBytes": 1234, "sizeHuman": "1.2 KB", "modifiedAt": "2026-06-24T…Z" }
    ],
    "rotated": [
      { "name": "iec-builder-out__2026-06-24_00-00-00.log.gz", "sizeBytes": 1234, "sizeHuman": "1.2 KB", "modifiedAt": "…Z" }
    ],
    "totalBytes": 2468,
    "totalHuman": "2.4 KB",
    "rotation": { "enabled": true, "maxSize": "50M", "retain": 14, "compress": true }
  }
}
```

- `files` — the two live streams (`out`, `error`).
- `rotated` — compressed archives in `~/.pm2/logs` matching `iec-builder-*`.
- `totalBytes` / `totalHuman` — sum of live + rotated.
- `rotation` — best-effort from `pm2 conf pm2-logrotate`; fields are `null` when undeterminable.

### `GET /platform/logs/tail?stream=out|error&lines=N&level=all|error|warn`

Tails a stream by reading **only the end of the file** (~2 KB budget per line — never the whole
762 MB file).

```jsonc
{
  "success": true,
  "data": {
    "stream": "out",
    "lines": [
      { "ts": "2026-06-24T…Z", "level": "error", "msg": "request errored", "raw": "{…redacted…}" }
    ],
    "returned": 1,
    "truncated": false
  }
}
```

| Query param | Default | Notes |
|-------------|---------|-------|
| `stream` | `out` | `out` or `error` |
| `lines` | `200` | clamped to a max of `1000` |
| `level` | `all` | `all`, `warn` (warn+error+fatal), or `error` (error+fatal) |

Each line is parsed as pino JSON for `ts` / `level` / `msg`; if parsing fails those fields are
`null` but `raw` is still redacted. `truncated` is `true` when older lines were dropped to fit the
requested `lines`.

## Key facts

- Redaction is two independent layers (write-time pino + read-time regex) — neither alone is
  sufficient because of historical lines.
- The logs API never returns secrets even when reading old, unredacted log content.
- PM2 log paths are resolved from `pm2 jlist` (`pm_out_log_path` / `pm_err_log_path`) with a
  `$HOME/.pm2/logs/iec-builder-{out,error}.log` fallback.

Install

Copy the skill content and save it to:

~/.claude/rules/tawa-builder-logs.md
Download .md

Coming soon via CLI:

tawa chaac install tawa-builder-logs

Details

Format
Rule
Category
deploy
Version
1.0.69292
Tokens
~1,023
Updated
2026-06-24
platformlogssecurityredactionrotationplatform-adminpm2