feat: dependency vulnerability scanning (#62) and Postman dev experience (#61)#124
Merged
nanaf6203-bit merged 13 commits intoJun 28, 2026
Conversation
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
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves two open issues assigned to @Moonwalker-rgb:
with threshold enforcement, plus a weekly drift cron
environments, with a Newman runner and a CI job
Why now
can land on
mainwithout anyone noticing until audit timePostman 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.ymlnpm audit --audit-level=high --omit=devsecurity-and-qualityon JavaScript / TypeScriptSNYK_TOKENis set;SARIF is uploaded to the GitHub code scanning dashboard
snyk/actions/setup@master.github/workflows/security-drift.yml(including devDependencies)
actions/github-script@v7to idempotently open / ping / closea security-labelled drift issue and create the label defensively
so it never 422s
.snyk— emptyignoreandpatchmaps with documented schemarequiring
reasonand an ISO 8601expires(max 90 days out)docs/SECURITY.md— full vulnerability management policy: severitythreshold, response SLA (critical 24h, high 7d, medium 30d),
suppression policy and local reproduction steps
package.json—audit:ci,audit:full,audit:fixscriptsPostman + Newman ( #61 )
postman/StellarTip.postman_collection.json(Postman v2.1.0){{accessToken}}Accept: application/json/profiles/me/analyticspayloadRefresh
postman/environments/{dev,staging,prod}.jsonscripts/run-postman.sh— Node 18+ gate, Newman--bail, globaldelay 200 ms (pairs with
THROTTLE_LIMIT: 1000in CI), CLI + JUnitreporters
.github/workflows/postman-tests.yml— boots Postgres + the API onlocalhost and runs
test:postman:devon every push and PR.gitignoreignores generated Newman reports.lintstagedrc.jsonformats Postman JSON on commitREADME.md— new "API exploration with Postman" and "Securityscanning" sections
Validation performed
npx tsc --noEmit— cleannpm run lint— cleannpx prettier --checkon the Postman JSON, YAML, SECURITY.md, README.md— clean
bash -n scripts/run-postman.sh— cleanpostman/artifacts — cleanidempotent drift issue lifecycle, valid
--orggating, labelexistence guard, GitHub-script replacement for the fragile heredoc,
Snyk action consistency,
npx --no-installto avoid global installs,lint-staged globs limited to files Prettier can actually format
Notes for reviewers
secrets.SNYK_TOKEN != '', so forks withoutan active Snyk subscription still pass CI
security. If the upstreamrepo prefers a more specific label, please share the name and I will
rename
.snykis intentionally empty: ignores and patches areadditions, never removals
Closes #62, closes #61.