@dhyasama/totem-merge 7.1.1 → 7.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ ## What and why
2
+
3
+ <!-- What does this change do, and why is it needed? -->
4
+
5
+ ## Author self-review
6
+
7
+ Totem has a single engineer, so this PR is self-reviewed under the single-engineer exception in the Change Management Policy. Before merging:
8
+
9
+ - [ ] I re-read the full diff after the last push
10
+ - [ ] The change was tested (how: )
11
+ - [ ] No secrets, credentials, or customer data are added (`gitleaks` passing)
12
+ - [ ] Access, IAM, or security-group changes are least-privilege and explained above
13
+ - [ ] Rollback plan is clear (revert this PR, or: )
14
+
15
+ After merge without peer approval, justify this PR on Vanta's Code Changes page.
@@ -0,0 +1,42 @@
1
+ name: Secret scan
2
+ # Fails if a push or pull request adds a credential. Scans only the new commits,
3
+ # with the rules in .gitleaks.toml; findings are redacted in the log.
4
+ on:
5
+ push:
6
+ pull_request:
7
+ permissions:
8
+ contents: read
9
+ jobs:
10
+ gitleaks:
11
+ runs-on: ubuntu-latest
12
+ timeout-minutes: 10
13
+ steps:
14
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
15
+ with:
16
+ fetch-depth: 0
17
+ persist-credentials: false
18
+ - name: Install gitleaks 8.30.1 (checksum verified)
19
+ run: |
20
+ curl -sSfL -o gitleaks.tgz https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
21
+ echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks.tgz" | sha256sum -c -
22
+ tar -xzf gitleaks.tgz gitleaks
23
+ - name: Scan the new commits
24
+ env:
25
+ EVENT: ${{ github.event_name }}
26
+ BEFORE: ${{ github.event.before }}
27
+ AFTER: ${{ github.sha }}
28
+ PR_BASE: ${{ github.event.pull_request.base.sha }}
29
+ PR_HEAD: ${{ github.event.pull_request.head.sha }}
30
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
31
+ run: |
32
+ if [ "$EVENT" = "pull_request" ]; then
33
+ RANGE="$PR_BASE..$PR_HEAD"
34
+ elif [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ] && git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
35
+ RANGE="$BEFORE..$AFTER"
36
+ elif git rev-parse --verify -q "origin/$DEFAULT_BRANCH" >/dev/null && [ "$(git rev-parse "origin/$DEFAULT_BRANCH")" != "$AFTER" ]; then
37
+ RANGE="origin/$DEFAULT_BRANCH..$AFTER"
38
+ else
39
+ RANGE="-1 $AFTER"
40
+ fi
41
+ echo "Scanning: $RANGE"
42
+ ./gitleaks git . --config .gitleaks.toml --log-opts="$RANGE" --redact --no-banner --exit-code 1
package/.gitleaks.toml ADDED
@@ -0,0 +1,197 @@
1
+ # Secret scanning rules for gitleaks (https://github.com/gitleaks/gitleaks).
2
+ # Used by .github/workflows/secret-scan.yml on every push and pull request.
3
+ # Defaults plus Totem-specific rules; values are never stored here, only patterns.
4
+ title = "Totem secret scan"
5
+
6
+ [extend]
7
+ useDefault = true
8
+
9
+ [[rules]]
10
+ id = "totem-personal-api-key"
11
+ description = "Totem personal API key (opaque tvc_ key)"
12
+ regex = '''\btvc_[A-Za-z0-9_-]{43}\b'''
13
+ keywords = ["tvc_"]
14
+
15
+ [[rules]]
16
+ id = "mongodb-uri-creds"
17
+ description = "MongoDB connection string with embedded credentials"
18
+ regex = '''mongodb(?:\+srv)?://([^:/\s'"@]+):([^@/\s'"]+)@'''
19
+ secretGroup = 2
20
+ keywords = ["mongodb"]
21
+
22
+ [[rules]]
23
+ id = "redis-uri-creds"
24
+ description = "Redis/Valkey URI with embedded password"
25
+ regex = '''rediss?://[^:/\s'"@]*:([^@/\s'"]+)@'''
26
+ secretGroup = 1
27
+ keywords = ["redis://", "rediss://"]
28
+
29
+ [[rules]]
30
+ id = "aiven-service-password"
31
+ description = "Aiven service password"
32
+ regex = '''AVNS_[A-Za-z0-9_-]{12,}'''
33
+ keywords = ["AVNS_"]
34
+
35
+ [[rules]]
36
+ id = "basic-auth-url"
37
+ description = "HTTP(S) URL with embedded basic-auth password"
38
+ regex = '''https?://[A-Za-z0-9._%+-]+:([^@/\s'"]{6,})@[A-Za-z0-9.-]+'''
39
+ secretGroup = 1
40
+ keywords = ["http://", "https://"]
41
+
42
+ [[rules]]
43
+ id = "google-api-key-any"
44
+ description = "Google API key (Maps, Gemini, etc.)"
45
+ regex = '''AIza[0-9A-Za-z_-]{35}'''
46
+ keywords = ["AIza"]
47
+
48
+ [[rules]]
49
+ id = "google-oauth-client-secret"
50
+ description = "Google OAuth client secret (new or legacy format)"
51
+ regex = '''(?i)(?:client_?secret|clientSecret)['"]?\s*[:=]\s*['"](GOCSPX-[A-Za-z0-9_-]{20,}|[A-Za-z0-9_-]{24})['"]'''
52
+ secretGroup = 1
53
+ keywords = ["client_secret", "clientsecret", "gocspx"]
54
+
55
+ [[rules]]
56
+ id = "gocspx-anywhere"
57
+ description = "Google OAuth client secret prefix"
58
+ regex = '''GOCSPX-[A-Za-z0-9_-]{20,}'''
59
+ keywords = ["GOCSPX-"]
60
+
61
+ [[rules]]
62
+ id = "openai-key-any"
63
+ description = "OpenAI API key"
64
+ regex = '''sk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}T3BlbkFJ[A-Za-z0-9_-]{20,}|sk-[A-Za-z0-9]{48}'''
65
+ keywords = ["sk-"]
66
+
67
+ [[rules]]
68
+ id = "aws-access-key-id-any"
69
+ description = "AWS access key ID"
70
+ regex = '''\b((?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA)[A-Z0-9]{16})\b'''
71
+ keywords = ["AKIA", "ASIA", "AGPA", "AIDA", "AROA", "AIPA", "ANPA", "ANVA"]
72
+
73
+ [[rules]]
74
+ id = "aws-secret-access-key"
75
+ description = "AWS secret access key assignment"
76
+ regex = '''(?i)(?:aws)?_?secret_?(?:access)?_?key['"]?\s*[:=]\s*['"]([A-Za-z0-9/+=]{40})['"]'''
77
+ secretGroup = 1
78
+ keywords = ["secret"]
79
+
80
+ [[rules]]
81
+ id = "private-key-block"
82
+ description = "PEM private key"
83
+ regex = '''-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY'''
84
+ keywords = ["PRIVATE KEY"]
85
+
86
+ [[rules]]
87
+ id = "gcp-service-account"
88
+ description = "GCP service account JSON"
89
+ regex = '''"private_key_id"\s*:\s*"[a-f0-9]{40}"'''
90
+ keywords = ["private_key_id"]
91
+
92
+ [[rules]]
93
+ id = "npm-token"
94
+ description = "npm access token"
95
+ regex = '''npm_[A-Za-z0-9]{36}|//registry\.npmjs\.org/:_authToken\s*=\s*([A-Za-z0-9-]{20,})'''
96
+ keywords = ["npm_", "_authToken"]
97
+
98
+ [[rules]]
99
+ id = "github-token"
100
+ description = "GitHub token"
101
+ regex = '''\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}\b|github_pat_[A-Za-z0-9_]{50,}'''
102
+ keywords = ["ghp_", "gho_", "ghu_", "ghs_", "ghr_", "github_pat_"]
103
+
104
+ [[rules]]
105
+ id = "slack-token"
106
+ description = "Slack token or webhook"
107
+ regex = '''xox[baprs]-[A-Za-z0-9-]{10,}|https://hooks\.slack\.com/services/T[A-Za-z0-9]+/B[A-Za-z0-9]+/[A-Za-z0-9]+'''
108
+ keywords = ["xox", "hooks.slack.com"]
109
+
110
+ [[rules]]
111
+ id = "stripe-key"
112
+ description = "Stripe live/test secret key"
113
+ regex = '''\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b'''
114
+ keywords = ["sk_live", "sk_test", "rk_live", "rk_test"]
115
+
116
+ [[rules]]
117
+ id = "sendgrid-key"
118
+ description = "SendGrid API key"
119
+ regex = '''SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}'''
120
+ keywords = ["SG."]
121
+
122
+ [[rules]]
123
+ id = "twilio-key"
124
+ description = "Twilio SID/key"
125
+ regex = '''\b(?:SK|AC)[a-f0-9]{32}\b'''
126
+ keywords = ["SK", "AC"]
127
+ entropy = 3.0
128
+
129
+ [[rules]]
130
+ id = "uuid-token-assignment"
131
+ description = "UUID-shaped token assigned near a secret-like keyword (Postmark, Logentries, Fixer, etc.)"
132
+ regex = '''(?i)(?:token|api_?key|apikey|secret|logentries|postmark|le_token)['"]?\s*[:=]\s*['"]([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})['"]'''
133
+ secretGroup = 1
134
+ keywords = ["token", "key", "secret", "logentries", "postmark"]
135
+
136
+ [[rules]]
137
+ id = "hex-or-b64-token-assignment"
138
+ description = "Long token/secret/apikey literal assigned in code"
139
+ regex = '''(?i)(?:api_?key|apikey|api_?secret|access_?token|auth_?token|secret_?key|client_?secret|private_?key|signing_?secret|jwt_?secret|session_?secret|cookie_?secret|encryption_?key|webhook_?secret)['"]?\s*[:=]\s*['"]([A-Za-z0-9_\-/+=.]{16,})['"]'''
140
+ secretGroup = 1
141
+ entropy = 3.2
142
+ keywords = ["key", "secret", "token"]
143
+
144
+ [[rules]]
145
+ id = "password-assignment"
146
+ description = "Password literal assigned in code"
147
+ regex = '''(?i)(?:password|passwd|pwd|pass)['"]?\s*[:=]\s*['"]([^'"\s]{8,})['"]'''
148
+ secretGroup = 1
149
+ entropy = 3.0
150
+ keywords = ["password", "passwd", "pwd", "pass"]
151
+
152
+ [[rules]]
153
+ id = "jwt-literal"
154
+ description = "JSON Web Token literal"
155
+ regex = '''\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b'''
156
+ keywords = ["eyJ"]
157
+
158
+ [[rules]]
159
+ id = "azure-storage-key"
160
+ description = "Azure storage account key"
161
+ regex = '''(?i)AccountKey\s*=\s*([A-Za-z0-9+/=]{86,88})'''
162
+ secretGroup = 1
163
+ keywords = ["AccountKey"]
164
+
165
+ [[rules]]
166
+ id = "generic-bearer"
167
+ description = "Hardcoded bearer token"
168
+ regex = '''(?i)['"]?authorization['"]?\s*[:=]\s*['"]Bearer\s+([A-Za-z0-9_\-.=]{20,})['"]'''
169
+ secretGroup = 1
170
+ keywords = ["bearer"]
171
+
172
+ [[allowlists]]
173
+ description = "Paths that are never source secrets"
174
+ paths = [
175
+ '''(^|/)node_modules/''',
176
+ '''(^|/)bower_components/''',
177
+ '''(^|/)\.next/''',
178
+ '''(^|/)dist/''',
179
+ '''(^|/)build/''',
180
+ '''(^|/)vendor/''',
181
+ '''package-lock\.json$''',
182
+ '''yarn\.lock$''',
183
+ '''pnpm-lock\.yaml$''',
184
+ '''\.min\.js$''',
185
+ '''\.map$''',
186
+ '''\.(png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|pdf|xlsx|docx|mp3|wav|zip)$''',
187
+ ]
188
+
189
+ [[allowlists]]
190
+ description = "Obvious placeholders"
191
+ regexes = [
192
+ '''(?i)process\.env''',
193
+ '''(?i)(your|my|example|sample|dummy|fake|placeholder|changeme|xxx+|\*{4,}|<[^>]+>)''',
194
+ '''(?i)mongodb(\+srv)?://(user|username|USER|USERNAME|<user>)[:@]''',
195
+ '''(?i)localhost''',
196
+ '''(?i)synthetic''',
197
+ ]
package/cli.js CHANGED
@@ -85,8 +85,8 @@ var buildProgram = function buildProgram() {
85
85
  console.log('Getting orgs...');
86
86
 
87
87
  async.parallel([
88
- Organization.getById.bind(Organization, target, { role: 'none' }),
89
- Organization.getById.bind(Organization, source, { role: 'none' })
88
+ Organization.getById.bind(Organization, target, { isWorkerProcess: true, includeLPs: false }),
89
+ Organization.getById.bind(Organization, source, { isWorkerProcess: true, includeLPs: false })
90
90
  ], function(err, results) {
91
91
 
92
92
  if (err) { done(err); }
package/index.js CHANGED
@@ -21,7 +21,6 @@ var Merge = function(mongoose, config) {
21
21
  this.Organization = mongoose.model('Organization');
22
22
  this.Person = mongoose.model('Person');
23
23
  this.Round = mongoose.model('Round');
24
- this.Snapshot = mongoose.model('Snapshot');
25
24
 
26
25
  // turn on debug if in development and not explicitly turned off
27
26
  mongoose.set('debug', (!config.suppressDebugLog && this.env == 'development'));
@@ -13,4 +13,4 @@ fallback in production or Lambda. Explicit development defaults remain unchanged
13
13
 
14
14
  See the [shared restart and cleanup runbook](https://github.com/Totem-VC/totem-services/blob/main/infrastructure/security-checks/runtime-secrets.md) for release verification, credential cleanup, and remaining limitations.
15
15
 
16
- Run `node --test infrastructure/security-checks/runtime-secret-contract.cjs` for isolated loader checks. These source changes require the normal application release to affect running processes. No credential was rotated.
16
+ Run `node --test infrastructure/security-checks/runtime-secret-contract.cjs` for isolated loader checks. Loader changes require a normal application release and process replacement to affect running processes.
package/lib/helpers.js CHANGED
@@ -4,20 +4,6 @@ var _ = require('underscore');
4
4
  var utilities = require('@dhyasama/totem-utilities');
5
5
  var phoneFormatter = require('phone-formatter');
6
6
 
7
- var createSnapshot = module.exports.createSnapshot = function createSnapshot(context, collectionName, doc, createdBy, cb) {
8
-
9
- context.logger.writeToInfoLog('Creating snapshot - ' + collectionName + ' - ' + createdBy + ' - ' + doc._id);
10
-
11
- var snapshot = new context.Snapshot({
12
- createdBy: createdBy,
13
- collectionName: collectionName,
14
- doc: doc
15
- });
16
-
17
- context.Snapshot.upsert(snapshot, cb);
18
-
19
- };
20
-
21
7
  // Generic, recursive object merging
22
8
  var mergeObjects = function mergeObjects(target, source, doNotMergeKeys) {
23
9
 
@@ -40,12 +40,6 @@ var merge = function merge(context, target, source, mergedBy, customerId, cb) {
40
40
 
41
41
  verifyOkToMerge.bind(null, target, source),
42
42
 
43
- // Create a snapshot of the target
44
- helpers.createSnapshot.bind(null, context, 'Organization', target, username),
45
-
46
- // Create a snapshot of the source
47
- helpers.createSnapshot.bind(null, context, 'Organization', source, username),
48
-
49
43
  // Merge the source into the target
50
44
  mergeSourceIntoTarget.bind(null, target, source),
51
45
 
@@ -64,10 +58,10 @@ var merge = function merge(context, target, source, mergedBy, customerId, cb) {
64
58
  if (err) { return cb(err, null); }
65
59
 
66
60
  // result from saveOrganization
67
- var updatedTarget = results[5];
61
+ var updatedTarget = results[3];
68
62
 
69
63
  // result from updateOrganizations within updateReferences
70
- var updatedReferencesOrgs = results[6][5];
64
+ var updatedReferencesOrgs = results[4][5];
71
65
 
72
66
  // Check if target had reference(s) updated
73
67
  var targetIdStr = target.id && target.id.toString();
package/lib/people.js CHANGED
@@ -33,12 +33,6 @@ var merge = function merge(context, target, source, mergedBy, customerId, cb) {
33
33
 
34
34
  async.series([
35
35
 
36
- // Create a snapshot of the target
37
- helpers.createSnapshot.bind(null, context, 'Person', target, username),
38
-
39
- // Create a snapshot of the source
40
- helpers.createSnapshot.bind(null, context, 'Person', source, username),
41
-
42
36
  // Merge the source into the target
43
37
  mergeSourceIntoTarget.bind(null, target, source),
44
38
 
@@ -57,7 +51,7 @@ var merge = function merge(context, target, source, mergedBy, customerId, cb) {
57
51
  if (err) return cb(err, null);
58
52
 
59
53
  // result from savePerson
60
- var updatedTarget = results[4];
54
+ var updatedTarget = results[2];
61
55
 
62
56
  // return updated target
63
57
  return cb(null, updatedTarget);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhyasama/totem-merge",
3
- "version": "7.1.1",
3
+ "version": "7.1.2",
4
4
  "description": "Org and person merging package",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -27,18 +27,9 @@
27
27
  "mongoose": "^8.24.3"
28
28
  },
29
29
  "devDependencies": {
30
- "@dhyasama/totem-models": "^12.14.0",
30
+ "@dhyasama/totem-models": "14.0.0",
31
31
  "mocha": "^12.0.2",
32
32
  "mocha-mongoose": "^1.1.1",
33
33
  "should": "^8.2.2"
34
- },
35
- "overrides": {
36
- "r7insight_node": {
37
- "lodash": "4.18.1",
38
- "semver": "5.7.2",
39
- "codependency": {
40
- "semver": "5.7.2"
41
- }
42
- }
43
34
  }
44
35
  }
package/test/index.js CHANGED
@@ -175,7 +175,7 @@ describeMerge('Merge', function() {
175
175
  should.not.exist(err);
176
176
  should.exist(newOrg1);
177
177
 
178
- Organization.getById(newOrg1.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, result1) {
178
+ Organization.getById(newOrg1.id, { CUSTOMER_ID: customer._id }, function(err, result1) {
179
179
 
180
180
  should.not.exist(err);
181
181
  should.exist(result1);
@@ -187,7 +187,7 @@ describeMerge('Merge', function() {
187
187
  should.not.exist(err);
188
188
  should.exist(newOrg2);
189
189
 
190
- Organization.getById(newOrg2.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, result2) {
190
+ Organization.getById(newOrg2.id, { CUSTOMER_ID: customer._id }, function(err, result2) {
191
191
 
192
192
  should.not.exist(err);
193
193
  should.exist(result2);
@@ -199,7 +199,7 @@ describeMerge('Merge', function() {
199
199
  should.not.exist(err);
200
200
  should.exist(newOrg3);
201
201
 
202
- Organization.getById(newOrg3.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, result3) {
202
+ Organization.getById(newOrg3.id, { CUSTOMER_ID: customer._id }, function(err, result3) {
203
203
 
204
204
  should.not.exist(err);
205
205
  should.exist(result3);
@@ -211,7 +211,7 @@ describeMerge('Merge', function() {
211
211
  should.not.exist(err);
212
212
  should.exist(newOrg4);
213
213
 
214
- Organization.getById(newOrg4.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, result4) {
214
+ Organization.getById(newOrg4.id, { CUSTOMER_ID: customer._id }, function(err, result4) {
215
215
 
216
216
  should.not.exist(err);
217
217
  should.exist(result4);
@@ -223,7 +223,7 @@ describeMerge('Merge', function() {
223
223
  should.not.exist(err);
224
224
  should.exist(newOrg5);
225
225
 
226
- Organization.getById(newOrg5.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, result5) {
226
+ Organization.getById(newOrg5.id, { CUSTOMER_ID: customer._id }, function(err, result5) {
227
227
 
228
228
  should.not.exist(err);
229
229
  should.exist(result5);
@@ -235,7 +235,7 @@ describeMerge('Merge', function() {
235
235
  should.not.exist(err);
236
236
  should.exist(newOrg6);
237
237
 
238
- Organization.getById(newOrg6.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, result6) {
238
+ Organization.getById(newOrg6.id, { CUSTOMER_ID: customer._id }, function(err, result6) {
239
239
 
240
240
  should.not.exist(err);
241
241
  should.exist(result6);
@@ -822,7 +822,7 @@ describeMerge('Merge', function() {
822
822
  should.not.exist(err);
823
823
  should.exist(merged);
824
824
 
825
- Organization.getById(result.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, finalResult) {
825
+ Organization.getById(result.id, { CUSTOMER_ID: customer._id }, function(err, finalResult) {
826
826
 
827
827
  should.not.exist(err);
828
828
  should.exist(finalResult);
@@ -883,7 +883,7 @@ describeMerge('Merge', function() {
883
883
  should.not.exist(err);
884
884
  should.exist(merged);
885
885
 
886
- Organization.getById(result.id, { CUSTOMER_ID: customer._id, role: 'none' }, function(err, finalResult) {
886
+ Organization.getById(result.id, { CUSTOMER_ID: customer._id }, function(err, finalResult) {
887
887
 
888
888
  should.not.exist(err);
889
889
  should.exist(finalResult);