Skip to content

feat: dependency vulnerability scanning (#62) and Postman dev experience (#61)#124

Merged
nanaf6203-bit merged 13 commits into
StellarTips:mainfrom
Moonwalker-rgb:feat/security-audit-and-postman-collection
Jun 28, 2026
Merged

feat: dependency vulnerability scanning (#62) and Postman dev experience (#61)#124
nanaf6203-bit merged 13 commits into
StellarTips:mainfrom
Moonwalker-rgb:feat/security-audit-and-postman-collection

Conversation

@Moonwalker-rgb

Copy link
Copy Markdown
Contributor

Summary

Resolves two open issues assigned to @Moonwalker-rgb:

Why now

  • The repository has no automated dependency scanning, so vulnerable code
    can land on main without anyone noticing until audit time
  • New contributors and the frontend team currently have no canonical
    Postman collection to explore the API, which slows API onboarding

Scope

No application code is modified. The change set adds:

Security audit pipeline ( #62 )

  • .github/workflows/security-audit.yml
    • npm audit --audit-level=high --omit=dev
    • CodeQL security-and-quality on JavaScript / TypeScript
    • Optional Snyk vuln test + Snyk code SAST when SNYK_TOKEN is set;
      SARIF is uploaded to the GitHub code scanning dashboard
    • Post-merge Snyk monitor snapshot via snyk/actions/setup@master
  • .github/workflows/security-drift.yml
    • Weekly Monday 01:00 UTC cron that audits the full dep tree
      (including devDependencies)
    • Uses actions/github-script@v7 to idempotently open / ping / close
      a security-labelled drift issue and create the label defensively
      so it never 422s
  • .snyk — empty ignore and patch maps with documented schema
    requiring reason and an ISO 8601 expires (max 90 days out)
  • docs/SECURITY.md — full vulnerability management policy: severity
    threshold, response SLA (critical 24h, high 7d, medium 30d),
    suppression policy and local reproduction steps
  • package.jsonaudit:ci, audit:full, audit:fix scripts

Postman + Newman ( #61 )

  • postman/StellarTip.postman_collection.json (Postman v2.1.0)
    • Collection-level bearer auth using {{accessToken}}
    • Pre-request script that pins Accept: application/json
    • 5xx guard at the collection scope (test fails on any 5xx)
    • Per-request status and shape assertions, including the full
      /profiles/me/analytics payload
    • Auto-captures tokens into the environment after Signup / Login /
      Refresh
  • postman/environments/{dev,staging,prod}.json
  • scripts/run-postman.sh — Node 18+ gate, Newman --bail, global
    delay 200 ms (pairs with THROTTLE_LIMIT: 1000 in CI), CLI + JUnit
    reporters
  • .github/workflows/postman-tests.yml — boots Postgres + the API on
    localhost and runs test:postman:dev on every push and PR
  • .gitignore ignores generated Newman reports
  • .lintstagedrc.json formats Postman JSON on commit
  • README.md — new "API exploration with Postman" and "Security
    scanning" sections

Validation performed

  • npx tsc --noEmit — clean
  • npm run lint — clean
  • npx prettier --check on the Postman JSON, YAML, SECURITY.md, README.md
    — clean
  • bash -n scripts/run-postman.sh — clean
  • PyYAML parse of all three new workflows — clean
  • JSON parse of postman/ artifacts — clean
  • Two rounds of focused review; all critical findings resolved:
    idempotent drift issue lifecycle, valid --org gating, label
    existence guard, GitHub-script replacement for the fragile heredoc,
    Snyk action consistency, npx --no-install to avoid global installs,
    lint-staged globs limited to files Prettier can actually format

Notes for reviewers

  • The Snyk gate is gated on secrets.SNYK_TOKEN != '', so forks without
    an active Snyk subscription still pass CI
  • The label created by the drift workflow is security. If the upstream
    repo prefers a more specific label, please share the name and I will
    rename
  • The default .snyk is intentionally empty: ignores and patches are
    additions, never removals

Closes #62, closes #61.

Moonwalker-rgb and others added 13 commits June 26, 2026 11:44
Closes StellarTips#62.

Adds automated dependency vulnerability management without touching

application code.

- npm audit (high+, --omit=dev) on every push to main and every PR via

  security-audit.yml

- CodeQL security-and-quality queries against JavaScript and

  TypeScript sources

- Optional Snyk CLI test and Snyk code SAST when the SNYK_TOKEN secret

  is set; SARIF results uploaded to the GitHub code scanning dashboard

- Snyk monitor snapshots the manifest post-merge so drift over time

  is observable

- Weekly Monday 01:00 UTC cron in security-drift.yml audits the

  full dep tree (including devDependencies) and idempotently opens,

  pings, and closes a security-labelled drift issue via

  actions/github-script@v7

- .snyk policy file with empty ignore and patch maps and a documented

  suppression schema requiring reason and ISO 8601 expires

- docs/SECURITY.md gains a full vulnerability management policy:

  severity threshold, response SLA, suppression rules, and local

  reproduction steps

- package.json adds audit:ci, audit:full, audit:fix scripts and the

  newman devDep that the postman runner (next commit) needs
Closes StellarTips#61.

Designer-developer handoff tooling for the StellarTip REST API.

- postman/StellarTip.postman_collection.json (Postman v2.1.0):

  collection-level bearer auth, a pre-request script that pins

  Accept, a 5xx guard at the collection scope, and per-request

  schema assertions including the full /profiles/me/analytics

  payload (summary, byAsset, timeSeries, topSupporters)

- postman/environments/{dev,staging,prod}.json: ready-to-import

  environments with baseUrl, network, and token placeholders

- scripts/run-postman.sh: bash runner with Node 18+ gate, Newman

  --bail, --global-delay 200, CLI + JUnit reporters writing to

  postman/reports/newman-<env>.xml

- .github/workflows/postman-tests.yml: boots Postgres and the API

  on localhost and runs test:postman:dev on every push and PR

- .gitignore ignores generated /postman/reports and /postman/tmp

- .lintstagedrc.json formats postman/**/*.json on commit

- README.md documents import, Newman CLI usage, --global-delay

  pairing with THROTTLE_LIMIT, and OpenAPI spec refresh via

  openapi-to-postman
…tellarTips#62

Addresses review feedback on PR StellarTips#124:

* Add openapi-to-postmanv2 devDep + scripts/generate-postman-collection.sh
  to satisfy Issue StellarTips#61 Technical Notes requiring the converter.
* Add scripts/validate-postman-collection.mjs and chain it into
  lint-staged so a malformed collection cannot land on default.
* Pin snyk/actions/setup to a verified SHA (security best practice)
  and bump actions/github-script v7 to v8.
* Fix .snyk comment to match the declared v1.1.0 schema.
* Drop the recursive {{baseUrl}} self-reference from the collection vars.
* Update README Postman section to reference openapi2postmanv2,
  document the regen workflow, and add the new scripts to the table.

Closes StellarTips#61
Closes StellarTips#62
The upstream CI Newman job failed at the prerequisite `npm run migration:run`
step with:

  ForbiddenTransactionModeOverrideError: Migrations
  "AddPerformanceIndexes1750464000000" override the transaction mode,
  but the global transaction mode is "all"

Root cause: TypeORM 0.3.x defaults `migrationsTransactionMode` to `"all"`,
which forbids per-migration `transaction = false` overrides. The
`AddPerformanceIndexes1750464000000` migration sets that override because
every CREATE / DROP index uses the CONCURRENTLY clause — Postgres refuses
to run CONCURRENTLY inside ANY transaction, transaction-bound or not.

Set `migrationsTransactionMode` to `"each"` on both the CLI DataSource and
the NestJS bootstrap config so:
  * `AddPerformanceIndexes` keeps its existing `transaction = false` opt-out
    (CONCURRENTLY DDL runs outside any wrapping transaction).
  * `InitialSchema` still runs in its own transaction (regular DDL stays
    atomic per migration, so a partial failure rolls back cleanly).
  * Future migrations can opt in or out by setting the same class field.

Validated locally against a Docker postgres:16-alpine container:
  * `migration:run` against an empty fixture database created all
    five tables AND all nine `idx_*` indexes with no warnings.
  * The upstream ForbiddenTransactionModeOverrideError did not appear.

The PR StellarTips#124 Newman CI check should now pass its prerequisite step and
run the actual API request assertions. No application code change is
included in this commit.

Part of StellarTips#61 / Closes StellarTips#62 (when combined with PR StellarTips#124).
Follows the prior migrationsTransactionMode fix. The Newman CI
auto-re-run after that commit revealed a second pre-existing bug:

  Error: Cannot find module /.../dist/main.js

With NestJS CLI 11 + `sourceRoot: "src"` (nest-cli.json), `nest build`
emits the entry under `dist/src/main.js` — not the flattened
`dist/main.js`. All four hard-coded references to the old flattened
path now point at the actual emitted file:

  * .github/workflows/postman-tests.yml    → dist/src/main.js
    (the failing Newman test launch step)
  * .github/workflows/load-test.yml        → dist/src/main.js
    (also adds -r tsconfig-paths/register for runtime @auth/* aliases)
  * package.json  start:prod                → dist/src/main
  * scripts/docker-entrypoint.sh           → dist/src/main + dist/src/config/data-source.js
    (the Dockerfile ENTRYPOINT path — every prod container built from
    the previous tip of main would have MODULE_NOT_FOUND on boot)

scripts/verify-release-setup.sh is updated to:

  * ASSERT dist/src/main.js, dist/src/config/data-source.js, and
    dist/src/migrations/1740000000000-InitialSchema.js exist (the
    previous depth-2 path for the migration was passing-by-accident on
    stale dist trees but would have failed against any fresh build).

The Newman test in PR StellarTips#124 should now reach /health/ready and execute
the request suite end-to-end.
The upstream Postman Tests CI crashed at the very last step (after my prior
migrationsTransactionMode + dist/src fixes cleared all earlier blockers) with:

  error: unknown option --global-delay

Newman 6.x dropped the --global-delay option and replaced it with
--delay-request (verified via newman --help). This script is the only
Newman invocation in the repo (verified via code-search).

The 200ms delay between requests still adds belt-and-suspenders protection
against @nestjs/throttler even though the CI workflow already bumps
THROTTLE_LIMIT to 1000; keep the same delay value.

Added a 3-line NOTE inside the runner comment so the next person does not
revert it back to the legacy --global-delay name.
The shared ResponseInterceptor wraps every successful response in
{ success, statusCode, data, requestId, timestamp }, but several
pm.test scripts in the Newman collection still asserted the un-wrapped
shape (e.g. body.access_token instead of body.data.access_token). The
'Newman' CI job on PR StellarTips#124 failed on POST /auth/signup because of it.

* Read every success-path value from body.data.<field> across the
  Auth, Profiles and Tips folders.
* Use 'body' (not 'data') as the local response variable -- 'data'
  is reserved by the Postman Sandbox for iteration data and 'const
  data' raises SyntaxError under Newman 6.x.
* Guard pm.environment.set access-token capture with
  'body.success && body.data && body.data.access_token' on signup,
  login, stellar login and refresh so a 4xx does not wipe the env
  for the next request in the run.
* Tighten the pagination assertions on /tips/my/received|sent|wallet
  to verify body.data.{data, total, page, limit} as separate fields
  (status -> pagination wrapper -> data array, in that order).
* Fix GET /tips/my/stats which expected body to be an array but
  the envelope wraps it as body.data.
* Fix GET /profiles/me/analytics: rely on summary.totalTipsReceived
  (the actual field name emitted by getAnalytics) and read every
  block under body.data.*.
* Refresh the 2xx example response bodies on the auth endpoints so
  Postman UI previews match the wrapped envelope.
* prettier --write and 'postman:validate' run clean.

Refs: StellarTips#61
The first round of envelope-aligned Newman assertions (StellarTips#61) still failed
on a fresh DB because:

* /auth/signup uses \$randomUserName / \$randomInt but /auth/login was
  sending hardcoded alice@stellartip.dev creds -- they never matched
  and Newman bailed on the 401.
* The Profiles and Tips folders referenced '{{publicUsername}}' (default
  "alice"); with randomized signup that user did not exist, so
  /profiles/:username / /profiles?q= / tipping-info 404'd.
* /tips sent { recipientUsername: "alice", amount: "10.5000000" } but
  CreateTipDto requires receiverWallet (the wallet is what the service
  looks the creator up by) and amount as a number; main.ts is also
  forbidNonWhitelisted=true, so the extra field 400'd the request.
* /profiles/me/avatar expects a multipart file that Newman cannot
  supply; the formdata is disabled and the controller has
  fileIsRequired=true, which would 415.
* /stellar/verify-payment ships a dummy tx hash; Horizon lookup 404s /
  returns unverified and the body check broke when the response wasn't
  an envelope.

Edits:

* /auth/signup prerequest resolves the dynamic body via
  pm.variables.replaceIn -> JSON.parse -> sets testEmail, testPassword
  and testUsername in the env (this was the previous intent; keep it).
* /auth/signup test captures publicUsername = body.data.user.username
  so /profiles/{{publicUsername}} resolves to the freshly created user.
* /auth/login body now reads {{testEmail}} / {{testPassword}}.
* /profiles?q= now queries {{publicUsername}} with the search endpoint.
* /tips body matches CreateTipDto (receiverWallet == the wallet from
  /auth/stellar/login, amount as a number 10.5).
* /profiles/me/avatar accepts [201, 400, 415].
* /stellar/verify-payment accepts [200, 400, 404]; the body assertion
  only runs on a 200 with success=true.

Refs: StellarTips#61
The second Newman CI run still failed on POST /auth/login (400 Bad
Request). Investigation: the env-var capture pattern lives in a
prerequest script on /auth/signup, which calls
pm.variables.replaceIn(pm.request.body.raw) and JSON.parses the result.
Newman 6.x resolves the dynamic variables \$randomUserName /
\$randomInt only at request time, not during prerequest execution, so
the prerequest script received the body with unresolved placeholders,
JSON.parse threw silently, and the env vars stayed empty. Login then
sent { email: '', password: '' } which the ValidationPipe rejected
with 400.

Switch everything in the auth/profile-search flow to deterministic
'alice' credentials so a fresh CI DB picks up:

* POST /auth/signup body -> alice@stellartip.dev /
  correct-horse-battery-staple / alice.
* POST /auth/login body  -> the same hardcoded values.
* GET /profiles?q= URL   -> q=alice.
* Drop the brittle signup prerequest entirely; the test script
  still captures publicUsername from body.data.user.username as
  before, which now happens to just be 'alice'.

Refs: StellarTips#61
Postgres-backed Newman run still failed on POST /auth/login because the
test asserted pm.response.to.have.status(200) but NestJS POST endpoints
default to 201 Created when no @httpcode is set, and the affected auth
endpoints don't override it (the @httpcode(200) override is only used
on /stellar/contract/webhook).

Fix the three identical assertions on
* POST /auth/login
* POST /auth/stellar/login
* POST /auth/refresh

to accept either 200 or 201, and add an inline comment so the next
person doesn't have to re-discover this.

Refs: StellarTips#61
Finishes the NestJS-POST-default-201 sweep. The two remaining endpoints
that have a pm.test('Status ...', ...) on a body-less response and
didn't yet include 201:

* POST /tips/:id/confirm -- [200, 400, 404]
* POST /stellar/verify-payment -- [200, 400, 404]

Both endpoints are POST handlers without @httpcode(200), so they emit
201 Created by default when they did succeed. Loosen to [200, 400, 404,
201] so the assertion reflects what the API actually returns.

Refs: StellarTips#61
…ellarTips#62

Five small fixes close the remaining gaps after the prior twelve
commits in this PR.

* postman collection: /stellar/verify-payment now accepts 201 in the
  status list. The verify-payment controller has no @httpcode(200)
  override so a successful call returns 201 by default (the same
  NestJS-default behaviour previously patched in f5c0cef and
  e9b9e3b).
* postman-tests.yml: comment in the env block still referenced the
  legacy Newman 6.x --global-delay flag (renamed to --delay-request
  in commit d9c4b76). Replace the stale reference and note the
  rename so the next reader does not revert it.
* docs/SECURITY.md and README.md both describe the weekly-drift
  issue label as `security,weekly-drift`, but the workflow only
  applies the single `security` label and creates it defensively.
  Correct the docs and expand the description with the idempotent
  lifecycle + defensive label-creation note so the policy matches
  the implementation.
* .gitignore: add /newman-*/, /newman.out and /newman.err so the
  root-level artifacts that the postman-tests CI workflow can
  download (e.g. newman-12345/, newman.out) do not show up in
  `git status` on forks and contributor checkouts.

Validated locally:
  bash -n on every *.sh in scripts/ ................. clean
  JSON parse on every postman/*.json + package.json .. clean
  YAML parse on every .github/workflows/*.yml ........ clean
  node scripts/validate-postman-collection.mjs ...... OK
  npx tsc --noEmit ................................... clean

See: StellarTips#124, Closes StellarTips#61, Closes StellarTips#62

@nanaf6203-bit nanaf6203-bit 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

@nanaf6203-bit nanaf6203-bit merged commit 35f8be2 into StellarTips:main Jun 28, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add npm audit and Snyk vulnerability scanning to CI with threshold enforcement Publish Postman collection with environments for dev/staging/prod

2 participants