Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# rc.2 (SGM-503, ASVS V14.2.1): Dependabot opens a pull request when a dependency has a newer version, and at once
# for a security advisory. Weekly, grouped per ecosystem so updates arrive as a few pull requests, not dozens. Each
# pull request runs the full CI, so a breaking update fails its checks and is not merged.
# pull request runs the full CI, so a breaking update fails its checks and is not merged. NuGet pull requests get their
# lock files regenerated by .github/workflows/dependabot-lockfiles.yml (OI-060).
version: 2
updates:
- package-ecosystem: nuget
Expand Down
85 changes: 85 additions & 0 deletions .github/workflows/dependabot-lockfiles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Dependabot lock files

# OI-060 (decision of 19 Oct 2026): Dependabot's NuGet pull requests change Directory.Packages.props and the lock files
# of the projects that reference a package directly, but not of the projects that get it through another project, so
# locked restore fails with NU1004 (as #47 did). This job regenerates every lock file on Dependabot's own NuGet branches
# and pushes them back to the pull request; CI then runs again on that commit.
#
# Safety: it runs only for pull requests Dependabot opened, from a branch in this repository whose name starts
# dependabot/nuget/, and only when Dependabot itself pushed (so its own push does not start it again). It runs
# `dotnet restore` and nothing else: no build, no tests. It pushes with a deploy key that can write to this repository
# only (secret LOCKFILE_DEPLOY_KEY; a push with the built-in token would not start CI again), and only to that branch.
# Lock files are the only thing it may change; anything else and it stops without pushing.

on:
pull_request_target:
types: [opened, synchronize, reopened]
branches: [main]

permissions:
contents: read

concurrency:
group: dependabot-lockfiles-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true

jobs:
lockfiles:
name: regenerate lock files
if: >-
github.actor == 'dependabot[bot]'
&& github.event.pull_request.user.login == 'dependabot[bot]'
&& github.event.pull_request.head.repo.full_name == github.repository
&& startsWith(github.event.pull_request.head.ref, 'dependabot/nuget/')
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Check out Dependabot's branch
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.ref }}
ssh-key: ${{ secrets.LOCKFILE_DEPLOY_KEY }}

- name: Set up .NET (from global.json)
uses: actions/setup-dotnet@v6
with:
global-json-file: global.json

- name: Regenerate every lock file
run: dotnet restore Sangam.sln --force-evaluate

- name: Check that only lock files changed
id: changes
run: |
changed="$(git status --porcelain)"
if [ -z "$changed" ]; then
echo "Lock files already match; nothing to push."
echo "push=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$changed"
if git status --porcelain | awk '{print $2}' | grep -v 'packages\.lock\.json$'; then
echo "::error::Something other than a lock file changed; not pushing."
exit 1
fi
echo "push=true" >> "$GITHUB_OUTPUT"

- name: Locked restore passes now
if: steps.changes.outputs.push == 'true'
run: dotnet restore Sangam.sln --locked-mode

- name: Push the lock files to the pull request
if: steps.changes.outputs.push == 'true'
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
git config user.name "sangam-lockfiles"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -- '*packages.lock.json'
git commit -m "Regenerate lock files for Dependabot's NuGet update"
git push origin "HEAD:${HEAD_REF}"
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ All notable changes to Sangam are recorded here. The format follows

## [Unreleased]

### Dependencies
- **Dependabot lock files** (OI-060): a workflow regenerates every lock file on Dependabot's NuGet pull requests and
pushes them back (deploy key `LOCKFILE_DEPLOY_KEY`), so those pull requests restore and run CI. It runs only `dotnet
restore`, only for Dependabot's own branches, and pushes only lock files.

## [1.0.0-rc.13] - thirteenth release candidate

Sangam 1.0.0-rc.13 answers what hospitals asked for after rc.12 (decisions of 14 Oct 2026): organisations that take only
Expand Down
3 changes: 3 additions & 0 deletions docs/go-live-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ exist, so there is no production database to act on yet.
enrolment and one PIN unlock on a real ward tablet before telling the hospitals.
- [ ] rc.12: applications that listen for webhooks add `session.locked` to their subscribed events if they show patient data
on shared devices.
- [ ] After rc.13 (OI-060): the deploy key "Dependabot lock files" exists with write access, and its private key is saved as
the `LOCKFILE_DEPLOY_KEY` secret for both Actions and Dependabot. Check that the next Dependabot NuGet pull
request gets a "Regenerate lock files" commit and passes CI.
- [ ] rc.13: migration `Rc13Tenancy` adds two columns (both off for every organisation).
- [ ] rc.13: tell each application's developers that `PUT` and `DELETE /api/v1/orgs/{id}/members/{user}` now need the
acting admin's own access token in `Sangam-Acting-Admin` (the SDKs' `setRoles`, `upsertMembership` and the new
Expand Down
7 changes: 7 additions & 0 deletions docs/security/dependency-scan.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ by the `security` CI job. It fails on anything high or critical.
| The `caddy:2` and `postgres:18` images | Trivy `image --ignore-unfixed` | reported, not failing (upstream's fixes) |
| The Kubernetes manifests (`deploy/kubernetes`) | `kubectl kustomize` and kubeconform (schemas); Trivy `misconfig` (above) | invalid manifests; high, critical |

Dependabot opens the weekly update pull requests (`.github/dependabot.yml`). Its NuGet pull requests update only the
lock files of projects that reference a package directly, so restore fails with NU1004 in the projects that get it
through another project. The **Dependabot lock files** workflow (`.github/workflows/dependabot-lockfiles.yml`, OI-060)
regenerates every lock file on Dependabot's NuGet branches and pushes them back, with the deploy key in the
`LOCKFILE_DEPLOY_KEY` secret (write access to this repository only); CI then runs on that commit. It runs `dotnet
restore` and nothing else, only for pull requests Dependabot opened, and pushes only lock files.

Run them yourself:

```sh
Expand Down
1 change: 1 addition & 0 deletions docs/security/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ that meet it in the code or the deployment, and what is left over. "Accepted" po
| E | A single operator approving their own risky action | Cooling-off and review for privileged recoveries | A second operator's approval is not yet required (planned) |
| S | An admin adding the wrong person, or an impostor, as staff | rc.12: adding needs an admin verified with DigiLocker (or the application's dated exception, each addition marked); the check says only which fields match an existing account (5 a person per admin a day, logged); the person joins with an activation code handed over in person or a join code from their own portal; an existing account whose name or date of birth differs stops and tells the admin | An admin who hands the code to the wrong person in person |
| R | An application changing who works where with nobody accountable | rc.13: `PUT` and `DELETE /orgs/{id}/members/{user}` name the admin who asked (their own access token, issued to that application); Sangam checks their right on the organisation's path, records them as `granted_by` and in the audit entry, and a new place needs them verified with DigiLocker | An application that misuses a signed-in admin's token, as it could misuse any of its own sessions |
| T | A workflow with write access running code from a pull request | After rc.13 (OI-060): the Dependabot lock-file workflow runs only for pull requests Dependabot opened and pushed, from this repository's `dependabot/nuget/` branches; it runs `dotnet restore` only (no build or tests), refuses to push anything but lock files, and its deploy key writes to this repository only | Restore evaluates the branch's project files; the branch is Dependabot's, and only people with write access could change it |
| E | Someone giving themselves a place at an organisation | rc.13: only where one of the application's administrators switched on "Testers may join themselves" for that one organisation (for a made-up demo hospital; audited); the person's own access token, issued to that application, names them; it never reaches the organisations under it, and a DigiLocker-only organisation still refuses the unverified | An administrator switching it on for a real hospital (go-live checklist checks it) |
| S | Someone unverified joining a hospital that asked for verified staff | rc.13: an organisation may take only people verified with DigiLocker (it holds for the organisations under it); an addition for anyone else waits and asks them to verify, joining is refused until they have, and every other way of giving a new place refuses too | People already there keep their place when it is switched on |
| E | A customer or branch admin reaching beyond their hospital | rc.12: admin rights are per organisation and cover only what is beneath it; every console list, change and device action checks the right on the organisation's path; a right ends with the person's last place in its tree; application admins alone close a customer | — |
Expand Down