diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..945c223 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,30 @@ +--- +name: development-security +description: Prepare safe authenticated development operations. +version: 0.1.0 +author: SKIC maintainers, Hermes Agent +license: MIT +platforms: [linux] +metadata: + hermes: + tags: [delivery, governance] + related_skills: [] +--- + +# Authenticated development boundary + +Owns a non-secret authorization and exposure preflight for development operations. The consumer's identity/security policy remains authoritative for credentials; this skill does not establish credential names, issue credentials or approve recovery. + +## When to use +Before authenticated development access, publication or collecting potentially credential-bearing failure evidence. + +## Procedure +1. Read the bound security policy and exact authorized operation. Record only target origin, purpose, permitted action and policy source in the work record. Stop when the policy cannot be loaded or the action is outside authority. +2. Classify the interface (existing trusted session, supported runtime API or trusted login form) and check the destination origin against that authorized target before credential use. Reject unexpected redirects or insecure transport; do not weaken TLS/MFA/access controls. +3. Check required secret availability by presence only under the governing policy. Missing injection is a capability blocker, not permission to create a fallback credential, borrow an identity or ask for secret values in chat. +4. Keep credentials out of command arguments, URLs, files, logs, screenshots, patches and test fixtures. Use only the policy-approved secure runtime interface. If an error may contain a secret, redact it before retaining diagnostic evidence; never inspect, hash or measure secret values for debugging. +5. Perform only the authorized operation. Authentication success is not authority to administer accounts, rotate credentials, alter permissions, spend, destroy resources or access unrelated data. +6. Verify the requested operation separately from login. Report target/purpose, success/failure and non-sensitive readback only. Follow the bound session-reuse/cleanup policy; never persist a new credential store as a convenience. + +## Pitfalls and verification +A valid token is capability, not consent; a generic 401/403 is not proof of a disabled account. Recovery requires a separately verified delegation and specialist procedure. Before handoff, inspect the intended evidence paths for credential exposure without printing secrets, verify the origin and action matched the authorization record, and retain no identity details unnecessary to the result. diff --git a/tests/contract.json b/tests/contract.json new file mode 100644 index 0000000..f2a9b0f --- /dev/null +++ b/tests/contract.json @@ -0,0 +1,9 @@ +[ + "Stop when the policy cannot be loaded", + "presence only", + "never inspect, hash or measure", + "Reject unexpected redirects", + "not authority to administer accounts", + "separately verified delegation", + "Never persist" +] diff --git a/tests/test_contract.py b/tests/test_contract.py new file mode 100644 index 0000000..2bb4377 --- /dev/null +++ b/tests/test_contract.py @@ -0,0 +1,21 @@ +"""Safeguard validation with negative mutation controls; no live operations.""" +from pathlib import Path +import json,unittest +ROOT=Path(__file__).resolve().parents[1] +CLAUSES=json.loads((Path(__file__).parent/'contract.json').read_text()) +def validate(text): + missing=[s for s in CLAUSES if s.lower() not in text.lower()] + if missing: raise ValueError(missing) +class Contract(unittest.TestCase): + def test_published_procedure_has_all_safeguards(self): + validate((ROOT/'SKILL.md').read_text()) + def test_each_removed_safeguard_is_rejected(self): + text=(ROOT/'SKILL.md').read_text().lower() + for s in CLAUSES: + with self.subTest(s=s),self.assertRaises(ValueError):validate(text.replace(s.lower(),'')) + def test_procedure_not_an_empty_router(self): + text=(ROOT/'SKILL.md').read_text() + self.assertIn('## Verification' if '## Verification' in text else 'verification',text) + self.assertTrue('Stop' in text or 'stop' in text or 'status unverified' in text) + self.assertTrue('## Procedure' in text or '## Failure-Repair Workflow' in text or '## Scope Status Vocabulary' in text) +if __name__=='__main__':unittest.main()