@goodandready/dsh-github-ops 0.6.1 → 0.6.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.
- package/lib/status.js +60 -7
- package/package.json +1 -1
package/lib/status.js
CHANGED
|
@@ -35,6 +35,21 @@ function isTrustedHost(host) {
|
|
|
35
35
|
return false
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Whether an address or host string represents local loopback. */
|
|
39
|
+
function isLoopback(val) {
|
|
40
|
+
if (!val || typeof val !== 'string') return false
|
|
41
|
+
let h = val.trim().toLowerCase()
|
|
42
|
+
if (h.startsWith('[')) {
|
|
43
|
+
const end = h.indexOf(']')
|
|
44
|
+
if (end > 0) h = h.slice(1, end)
|
|
45
|
+
} else if (h.includes(':')) {
|
|
46
|
+
const colons = h.split(':').length - 1
|
|
47
|
+
if (colons === 1) h = h.split(':')[0]
|
|
48
|
+
}
|
|
49
|
+
const clean = h.replace(/^::ffff:/, '')
|
|
50
|
+
return clean === '127.0.0.1' || clean === '::1' || clean.startsWith('127.') || clean === 'localhost' || clean === 'localhost.'
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
/** Whether a request may read the status payload. Fail-closed. */
|
|
39
54
|
export function isTrustedRequest(req) {
|
|
40
55
|
if (!req) return false
|
|
@@ -51,15 +66,13 @@ export function isTrustedRequest(req) {
|
|
|
51
66
|
if (cleanIp && !isTrustedHost(cleanIp)) return false
|
|
52
67
|
|
|
53
68
|
const host = header(req, 'host')
|
|
54
|
-
const origin = header(req, 'origin') || header(req, 'referer')
|
|
55
|
-
|
|
56
69
|
// If host is provided, it MUST be a trusted local/private host (DNS rebinding guard)
|
|
57
70
|
if (host && !isTrustedHost(host)) return false
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
const origin = header(req, 'origin')
|
|
73
|
+
const referer = header(req, 'referer')
|
|
61
74
|
|
|
62
|
-
//
|
|
75
|
+
// Check origin header first
|
|
63
76
|
if (origin) {
|
|
64
77
|
try {
|
|
65
78
|
const originUrl = new URL(origin)
|
|
@@ -71,6 +84,31 @@ export function isTrustedRequest(req) {
|
|
|
71
84
|
}
|
|
72
85
|
}
|
|
73
86
|
|
|
87
|
+
// Check referer header
|
|
88
|
+
if (referer) {
|
|
89
|
+
try {
|
|
90
|
+
const refUrl = new URL(referer)
|
|
91
|
+
if (refUrl.protocol === 'http:' || refUrl.protocol === 'https:') {
|
|
92
|
+
if (!isTrustedHost(refUrl.hostname)) return false
|
|
93
|
+
if (host && refUrl.host.toLowerCase() !== host.toLowerCase()) return false
|
|
94
|
+
return true
|
|
95
|
+
}
|
|
96
|
+
// Non-http referer (e.g. dsh-app://app/ in DSH Desktop Electron):
|
|
97
|
+
// Desktop removes origin/sec-fetch-site and sets custom scheme referer.
|
|
98
|
+
// Accept only if caller is loopback (socket or loopback host).
|
|
99
|
+
const hostIsLoopback = host ? isLoopback(host) : false
|
|
100
|
+
if (loopback || (!address && hostIsLoopback)) {
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
return false
|
|
104
|
+
} catch {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// If browser headers (origin/referer/sec-fetch-site) are absent and caller is loopback, allow local caller
|
|
110
|
+
if (!secFetchSite && loopback) return true
|
|
111
|
+
|
|
74
112
|
// Non-loopback caller without origin/referer is refused
|
|
75
113
|
if (!loopback) return false
|
|
76
114
|
|
|
@@ -94,7 +132,9 @@ export function isTrustedWriteRequest(req) {
|
|
|
94
132
|
if (cleanIp && !isTrustedHost(cleanIp)) return false
|
|
95
133
|
|
|
96
134
|
const host = header(req, 'host')
|
|
97
|
-
const origin = header(req, 'origin')
|
|
135
|
+
const origin = header(req, 'origin')
|
|
136
|
+
const referer = header(req, 'referer')
|
|
137
|
+
|
|
98
138
|
if (origin) {
|
|
99
139
|
try {
|
|
100
140
|
const originUrl = new URL(origin)
|
|
@@ -102,8 +142,21 @@ export function isTrustedWriteRequest(req) {
|
|
|
102
142
|
} catch {
|
|
103
143
|
return false
|
|
104
144
|
}
|
|
145
|
+
} else if (referer) {
|
|
146
|
+
try {
|
|
147
|
+
const refUrl = new URL(referer)
|
|
148
|
+
if (refUrl.protocol === 'http:' || refUrl.protocol === 'https:') {
|
|
149
|
+
if (host && refUrl.host.toLowerCase() !== host.toLowerCase()) return false
|
|
150
|
+
} else {
|
|
151
|
+
// Non-http custom scheme referer from loopback: permitted as local loopback writer
|
|
152
|
+
const hostIsLoopback = host ? isLoopback(host) : false
|
|
153
|
+
if (!loopback && (address || !hostIsLoopback)) return false
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
105
158
|
} else {
|
|
106
|
-
// Write request without origin: only permit local loopback
|
|
159
|
+
// Write request without origin/referer: only permit local loopback
|
|
107
160
|
if (!loopback) return false
|
|
108
161
|
}
|
|
109
162
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-github-ops",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "GitHub operations for DeepSeek Harness: releases, tags, a guarded generic API pass-through, workflow runs, secrets and sanitized mirror publication.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|