Q:

Does this use of hash provide anything over a nonce? HMAC instead?

I ran across some CSRF code that does:
Tokens.prototype._tokenize = function tokenize (secret, salt) {
  return salt + '-' + hash(salt + '-' + secret)
}

where hash seems to be a SHA1 digest modified to fit in a multipart/form-data body without extra encoding:
function hash (str) {
  return crypto
    .createHash('sha1')
    .update(str, 'ascii')
    .digest('base64')
    .replace(PLUS_GLOBAL_REGEXP, '-')
    .replace(SLASH_GLOBAL_REGEXP, '_')
    .replace(EQUAL_GLOBAL_REGEXP, '')
}

IIUC, the use of hash doesn't prevent forging.
Does this provide much over just using a closely held nonce?
Does hash make it particularly difficult to derive the input?  Since secret is not encrypted, this seems to assume that either

secret need not be secret or
the token is closely held, which should be the case for a CSRF token.

A:

the use of hash doesn't prevent forging. Does this provide much over just using a closely held nonce?

on the contrary, the use of hash described should prevent forging assuming the secret is kept .. well.. secret

Does hash make it particularly difficult to derive the input? Since secret is not encrypted, this seems to assume that either

Indeed, it should be very hard to derive input (secret) from its hash. That's one of the mandatory properties of a hash function. The secret should stay secret on the server side (not revealed to the client)

secret need not be secret or the token is closely held, which should be the case for a CSRF token.

if the secret is revealed, the client could forge a new valid CSRF token

