CC secrets build: consumers switched to sec, S19 age key in the recovery runbooks, evidence (R-1118, R-1122)
gates / gates (push) Successful in 6m44s
gates / gates (push) Successful in 6m44s
Build skill (hub token, ISO publish), fb_probe (token from `sec run` env, tests red-proved), secrets.md (Resend: pipe inside sec run), bench README, read_credential docstring, 12-agent-tooling §5, workspace CLAUDE secrets line. Evidence: audits/cc-creds-build-2026-10-17/ (tests, red-proofs, 36-row child-session proof both launch modes, install, fresh-session proof, import names+lengths, history sweep names). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
@@ -14,7 +14,8 @@ Usage: python3 scripts/facebook/fb_probe.py [-v] [--version v26.0] [--evidence
|
||||
There is deliberately NO "post for real" sub-command: real posting belongs to the skill written
|
||||
from this spike's findings (audits/SPIKE-facebook-page-api-2026-10-08.md).
|
||||
|
||||
SECRETS. The token comes ONLY from ~/.config/credentials (key FACEBOOK_API); no flag takes one.
|
||||
SECRETS. The token comes from the CC secret store — run `sec run FACEBOOK_API -- python3 fb_probe.py …` — or,
|
||||
until the old file retires, from ~/.config/credentials (key FACEBOOK_API); no flag takes one.
|
||||
It travels as `Authorization: Bearer`, never in a logged URL (the one query-string carrier,
|
||||
debug_token's input_token, is never printed: -v prints the path without its query). Every saved
|
||||
response passes redact(): every `access_token` key is dropped and any string carrying a held token
|
||||
@@ -78,6 +79,17 @@ def vlog(msg):
|
||||
|
||||
# ---------------------------------------------------------------- the key (R-453)
|
||||
|
||||
def check_value(value):
|
||||
"""Assert the shape of a Meta token: no quote, no whitespace, starts with EAA."""
|
||||
if any(q in value for q in ("'", '"')):
|
||||
raise CredentialError("value still contains a quote character")
|
||||
if any(c.isspace() for c in value):
|
||||
raise CredentialError("value contains whitespace")
|
||||
if not value.startswith("EAA"):
|
||||
raise CredentialError("value does not start with EAA (starts %r)" % value[:3])
|
||||
return value
|
||||
|
||||
|
||||
def load_key(path, key=KEY):
|
||||
"""Return the value of `key`. Accepts an optional `export ` prefix; ONE matching quote pair is
|
||||
stripped by read_credential.unwrap. Then asserts: no quote, no whitespace, starts with EAA."""
|
||||
@@ -87,17 +99,18 @@ def load_key(path, key=KEY):
|
||||
if s.startswith("export "):
|
||||
s = s[len("export "):].lstrip()
|
||||
if s.startswith(key + "="):
|
||||
value = unwrap(s[len(key) + 1:].strip())
|
||||
if any(q in value for q in ("'", '"')):
|
||||
raise CredentialError("value still contains a quote character")
|
||||
if any(c.isspace() for c in value):
|
||||
raise CredentialError("value contains whitespace")
|
||||
if not value.startswith("EAA"):
|
||||
raise CredentialError("value does not start with EAA (starts %r)" % value[:3])
|
||||
return value
|
||||
return check_value(unwrap(s[len(key) + 1:].strip()))
|
||||
raise CredentialError("key %r not present in %s" % (key, path))
|
||||
|
||||
|
||||
def token_from(env, path, key=KEY):
|
||||
"""The token: from the environment when `sec run FACEBOOK_API -- python3 fb_probe.py …` put it there (the
|
||||
CC secret store, cc-secrets ARCHITECTURE.md); otherwise from the old credentials file, until that file retires."""
|
||||
if key in env:
|
||||
return check_value(env[key]), "environment (sec run)"
|
||||
return load_key(path, key), path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- redaction
|
||||
|
||||
def redact(obj, secrets=None):
|
||||
@@ -569,12 +582,12 @@ def main(argv=None):
|
||||
a = build_parser().parse_args(argv)
|
||||
VERBOSE = a.v
|
||||
try:
|
||||
token = load_key(a.credentials)
|
||||
token, source = token_from(os.environ, a.credentials)
|
||||
except (CredentialError, OSError) as e:
|
||||
log("KEY REFUSED [%s]: %s" % (KEY, e))
|
||||
return 2
|
||||
SECRETS.append(token)
|
||||
log("key %s: %d chars, starts %s" % (KEY, len(token), token[:3]))
|
||||
log("key %s: %d chars, starts %s, from %s" % (KEY, len(token), token[:3], source))
|
||||
# write-test re-derives A and B into its own sub-directory, so the read run's files stay as they were
|
||||
sub = {"read": "", "write-test": "write-test"}.get(a.cmd, "first-post")
|
||||
g = Graph(a.version, a.evidence if not sub else os.path.join(a.evidence, sub))
|
||||
|
||||
Reference in New Issue
Block a user